LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mapml - ogrmapmldataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 634 648 97.8 %
Date: 2024-11-21 22:18:42 Functions: 38 38 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  MapML Translator
       5             :  * Author:   Even Rouault, Even Rouault <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys dot com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_minixml.h"
      14             : #include "gdal_pam.h"
      15             : #include "ogrsf_frmts.h"
      16             : 
      17             : #include <map>
      18             : #include <memory>
      19             : #include <set>
      20             : #include <vector>
      21             : 
      22             : constexpr int EPSG_CODE_WGS84 = 4326;
      23             : constexpr int EPSG_CODE_CBMTILE = 3978;
      24             : constexpr int EPSG_CODE_APSTILE = 5936;
      25             : constexpr int EPSG_CODE_OSMTILE = 3857;
      26             : 
      27             : static const struct
      28             : {
      29             :     int nEPSGCode;
      30             :     const char *pszName;
      31             : } asKnownCRS[] = {
      32             :     {EPSG_CODE_WGS84, "WGS84"},
      33             :     {EPSG_CODE_CBMTILE, "CBMTILE"},
      34             :     {EPSG_CODE_APSTILE, "APSTILE"},
      35             :     {EPSG_CODE_OSMTILE, "OSMTILE"},
      36             : };
      37             : 
      38             : /************************************************************************/
      39             : /*                     OGRMapMLReaderDataset                            */
      40             : /************************************************************************/
      41             : 
      42             : class OGRMapMLReaderLayer;
      43             : 
      44             : class OGRMapMLReaderDataset final : public GDALPamDataset
      45             : {
      46             :     friend class OGRMapMLReaderLayer;
      47             : 
      48             :     std::vector<std::unique_ptr<OGRMapMLReaderLayer>> m_apoLayers{};
      49             :     CPLXMLTreeCloser m_oRootCloser{nullptr};
      50             :     CPLString m_osDefaultLayerName{};
      51             : 
      52             :   public:
      53          71 :     int GetLayerCount() override
      54             :     {
      55          71 :         return static_cast<int>(m_apoLayers.size());
      56             :     }
      57             : 
      58             :     OGRLayer *GetLayer(int idx) override;
      59             : 
      60             :     static int Identify(GDALOpenInfo *poOpenInfo);
      61             :     static GDALDataset *Open(GDALOpenInfo *);
      62             : };
      63             : 
      64             : /************************************************************************/
      65             : /*                         OGRMapMLReaderLayer                          */
      66             : /************************************************************************/
      67             : 
      68             : class OGRMapMLReaderLayer final
      69             :     : public OGRLayer,
      70             :       public OGRGetNextFeatureThroughRaw<OGRMapMLReaderLayer>
      71             : {
      72             :     OGRMapMLReaderDataset *m_poDS = nullptr;
      73             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
      74             :     OGRSpatialReference *m_poSRS = nullptr;
      75             : 
      76             :     // not to be destroyed
      77             :     CPLXMLNode *m_psBody = nullptr;
      78             :     CPLXMLNode *m_psCurNode = nullptr;
      79             :     GIntBig m_nFID = 1;
      80             : 
      81             :     OGRFeature *GetNextRawFeature();
      82             : 
      83             :   public:
      84             :     OGRMapMLReaderLayer(OGRMapMLReaderDataset *poDS, const char *pszLayerName);
      85             :     ~OGRMapMLReaderLayer();
      86             : 
      87        1078 :     OGRFeatureDefn *GetLayerDefn() override
      88             :     {
      89        1078 :         return m_poFeatureDefn;
      90             :     }
      91             : 
      92             :     void ResetReading() override;
      93         375 :     DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRMapMLReaderLayer)
      94             :     int TestCapability(const char *pszCap) override;
      95             : 
      96           1 :     GDALDataset *GetDataset() override
      97             :     {
      98           1 :         return m_poDS;
      99             :     }
     100             : };
     101             : 
     102             : /************************************************************************/
     103             : /*                        OGRMapMLWriterDataset                         */
     104             : /************************************************************************/
     105             : 
     106             : class OGRMapMLWriterLayer;
     107             : 
     108             : class OGRMapMLWriterDataset final : public GDALPamDataset
     109             : {
     110             :     friend class OGRMapMLWriterLayer;
     111             : 
     112             :     VSILFILE *m_fpOut = nullptr;
     113             :     std::vector<std::unique_ptr<OGRMapMLWriterLayer>> m_apoLayers{};
     114             :     CPLXMLNode *m_psRoot = nullptr;
     115             :     CPLString m_osExtentUnits{};
     116             :     OGRSpatialReference m_oSRS{};
     117             :     OGREnvelope m_sExtent{};
     118             :     CPLStringList m_aosOptions{};
     119             :     const char *m_pszFormatCoordTuple = nullptr;
     120             : 
     121             :     // not to be destroyed
     122             :     CPLXMLNode *m_psExtent = nullptr;
     123             :     CPLXMLNode *m_psLastChild = nullptr;
     124             : 
     125             :   public:
     126             :     explicit OGRMapMLWriterDataset(VSILFILE *fpOut);
     127             :     ~OGRMapMLWriterDataset() override;
     128             : 
     129           3 :     int GetLayerCount() override
     130             :     {
     131           3 :         return static_cast<int>(m_apoLayers.size());
     132             :     }
     133             : 
     134             :     OGRLayer *GetLayer(int idx) override;
     135             : 
     136             :     OGRLayer *ICreateLayer(const char *pszName,
     137             :                            const OGRGeomFieldDefn *poGeomFieldDefn,
     138             :                            CSLConstList papszOptions) override;
     139             : 
     140             :     int TestCapability(const char *) override;
     141             : 
     142             :     void AddFeature(CPLXMLNode *psNode);
     143             : 
     144             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
     145             :                                int nBandsIn, GDALDataType eDT,
     146             :                                char **papszOptions);
     147             : };
     148             : 
     149             : /************************************************************************/
     150             : /*                         OGRMapMLWriterLayer                          */
     151             : /************************************************************************/
     152             : 
     153             : class OGRMapMLWriterLayer final : public OGRLayer
     154             : {
     155             :     OGRMapMLWriterDataset *m_poDS = nullptr;
     156             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
     157             :     GIntBig m_nFID = 1;
     158             :     std::unique_ptr<OGRCoordinateTransformation> m_poCT{};
     159             : 
     160             :     void writeLineStringCoordinates(CPLXMLNode *psContainer,
     161             :                                     const OGRLineString *poLS);
     162             :     void writePolygon(CPLXMLNode *psContainer, const OGRPolygon *poPoly);
     163             :     void writeGeometry(CPLXMLNode *psContainer, const OGRGeometry *poGeom,
     164             :                        bool bInGeometryCollection);
     165             : 
     166             :   public:
     167             :     OGRMapMLWriterLayer(OGRMapMLWriterDataset *poDS, const char *pszLayerName,
     168             :                         std::unique_ptr<OGRCoordinateTransformation> &&poCT);
     169             :     ~OGRMapMLWriterLayer();
     170             : 
     171         429 :     OGRFeatureDefn *GetLayerDefn() override
     172             :     {
     173         429 :         return m_poFeatureDefn;
     174             :     }
     175             : 
     176          17 :     void ResetReading() override
     177             :     {
     178          17 :     }
     179             : 
     180          17 :     OGRFeature *GetNextFeature() override
     181             :     {
     182          17 :         return nullptr;
     183             :     }
     184             : 
     185             :     OGRErr CreateField(const OGRFieldDefn *poFieldDefn, int) override;
     186             :     OGRErr ICreateFeature(OGRFeature *poFeature) override;
     187             :     int TestCapability(const char *) override;
     188             : 
     189          17 :     GDALDataset *GetDataset() override
     190             :     {
     191          17 :         return m_poDS;
     192             :     }
     193             : };
     194             : 
     195             : /************************************************************************/
     196             : /*                             Identify()                               */
     197             : /************************************************************************/
     198             : 
     199       44724 : int OGRMapMLReaderDataset::Identify(GDALOpenInfo *poOpenInfo)
     200             : {
     201       47076 :     return poOpenInfo->pabyHeader != nullptr &&
     202        2352 :            strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     203       44724 :                   "<mapml>") != nullptr;
     204             : }
     205             : 
     206             : /************************************************************************/
     207             : /*                               Open()                                 */
     208             : /************************************************************************/
     209             : 
     210          49 : GDALDataset *OGRMapMLReaderDataset::Open(GDALOpenInfo *poOpenInfo)
     211             : {
     212          49 :     if (!Identify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
     213           0 :         return nullptr;
     214          49 :     CPLXMLNode *psRoot = CPLParseXMLFile(poOpenInfo->pszFilename);
     215          98 :     CPLXMLTreeCloser oRootCloser(psRoot);
     216          49 :     if (psRoot == nullptr)
     217           1 :         return nullptr;
     218          48 :     CPLXMLNode *psBody = CPLGetXMLNode(psRoot, "=mapml.body");
     219          48 :     if (psBody == nullptr)
     220           1 :         return nullptr;
     221          94 :     const CPLString osDefaultLayerName(CPLGetBasename(poOpenInfo->pszFilename));
     222          94 :     std::set<std::string> oSetLayerNames;
     223         214 :     for (auto psNode = psBody->psChild; psNode; psNode = psNode->psNext)
     224             :     {
     225         167 :         if (psNode->eType != CXT_Element ||
     226         167 :             strcmp(psNode->pszValue, "feature") != 0)
     227             :         {
     228          45 :             continue;
     229             :         }
     230             :         const char *pszClass =
     231         122 :             CPLGetXMLValue(psNode, "class", osDefaultLayerName.c_str());
     232         122 :         oSetLayerNames.insert(pszClass);
     233             :     }
     234          47 :     if (oSetLayerNames.empty())
     235          17 :         return nullptr;
     236          30 :     auto poDS = new OGRMapMLReaderDataset();
     237          30 :     poDS->m_osDefaultLayerName = osDefaultLayerName;
     238          30 :     poDS->m_oRootCloser = std::move(oRootCloser);
     239          77 :     for (const auto &layerName : oSetLayerNames)
     240             :     {
     241          94 :         poDS->m_apoLayers.emplace_back(std::unique_ptr<OGRMapMLReaderLayer>(
     242          94 :             new OGRMapMLReaderLayer(poDS, layerName.c_str())));
     243             :     }
     244          30 :     return poDS;
     245             : }
     246             : 
     247             : /************************************************************************/
     248             : /*                             GetLayer()                               */
     249             : /************************************************************************/
     250             : 
     251          40 : OGRLayer *OGRMapMLReaderDataset::GetLayer(int idx)
     252             : {
     253          40 :     return idx >= 0 && idx < GetLayerCount() ? m_apoLayers[idx].get() : nullptr;
     254             : }
     255             : 
     256             : /************************************************************************/
     257             : /*                         OGRMapMLReaderLayer()                        */
     258             : /************************************************************************/
     259             : 
     260          47 : OGRMapMLReaderLayer::OGRMapMLReaderLayer(OGRMapMLReaderDataset *poDS,
     261          47 :                                          const char *pszLayerName)
     262          47 :     : m_poDS(poDS)
     263             : {
     264          47 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
     265          47 :     m_poFeatureDefn->Reference();
     266          47 :     SetDescription(pszLayerName);
     267             : 
     268          47 :     m_psBody = CPLGetXMLNode(poDS->m_oRootCloser.get(), "=mapml.body");
     269          47 :     m_psCurNode = m_psBody->psChild;
     270             : 
     271          47 :     const char *pszUnits = CPLGetXMLValue(m_psBody, "extent.units", nullptr);
     272          47 :     if (pszUnits)
     273             :     {
     274          49 :         for (const auto &knownCRS : asKnownCRS)
     275             :         {
     276          49 :             if (strcmp(pszUnits, knownCRS.pszName) == 0)
     277             :             {
     278          46 :                 m_poSRS = new OGRSpatialReference();
     279          46 :                 m_poSRS->importFromEPSG(knownCRS.nEPSGCode);
     280          46 :                 m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     281          46 :                 break;
     282             :             }
     283             :         }
     284             :     }
     285          47 :     m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
     286             : 
     287             :     // Guess layer geometry type and establish fields
     288          47 :     bool bMixed = false;
     289          47 :     OGRwkbGeometryType eLayerGType = wkbUnknown;
     290          94 :     std::vector<std::string> aosFieldNames;
     291          94 :     std::map<std::string, OGRFieldType> oMapFieldTypes;
     292         298 :     while (m_psCurNode != nullptr)
     293             :     {
     294         753 :         if (m_psCurNode->eType == CXT_Element &&
     295         456 :             strcmp(m_psCurNode->pszValue, "feature") == 0 &&
     296         205 :             strcmp(CPLGetXMLValue(m_psCurNode, "class",
     297         205 :                                   m_poDS->m_osDefaultLayerName.c_str()),
     298         205 :                    m_poFeatureDefn->GetName()) == 0)
     299             :         {
     300             :             const CPLXMLNode *psGeometry =
     301         122 :                 CPLGetXMLNode(m_psCurNode, "geometry");
     302         122 :             if (!bMixed && psGeometry && psGeometry->psChild &&
     303          78 :                 psGeometry->psChild->eType == CXT_Element)
     304             :             {
     305          78 :                 OGRwkbGeometryType eGType = wkbUnknown;
     306          78 :                 const char *pszType = psGeometry->psChild->pszValue;
     307          78 :                 if (EQUAL(pszType, "point"))
     308          12 :                     eGType = wkbPoint;
     309          66 :                 else if (EQUAL(pszType, "linestring"))
     310          10 :                     eGType = wkbLineString;
     311          56 :                 else if (EQUAL(pszType, "polygon"))
     312          27 :                     eGType = wkbPolygon;
     313          29 :                 else if (EQUAL(pszType, "multipoint"))
     314           7 :                     eGType = wkbMultiPoint;
     315          22 :                 else if (EQUAL(pszType, "multilinestring"))
     316           7 :                     eGType = wkbMultiLineString;
     317          15 :                 else if (EQUAL(pszType, "multipolygon"))
     318           7 :                     eGType = wkbMultiPolygon;
     319           8 :                 else if (EQUAL(pszType, "geometrycollection"))
     320           7 :                     eGType = wkbGeometryCollection;
     321          78 :                 if (eLayerGType == wkbUnknown)
     322          44 :                     eLayerGType = eGType;
     323          34 :                 else if (eLayerGType != eGType)
     324             :                 {
     325          16 :                     eLayerGType = wkbUnknown;
     326          16 :                     bMixed = true;
     327             :                 }
     328             :             }
     329             : 
     330             :             const CPLXMLNode *psTBody =
     331         122 :                 CPLGetXMLNode(m_psCurNode, "properties.div.table.tbody");
     332         122 :             if (psTBody)
     333             :             {
     334         529 :                 for (const CPLXMLNode *psCur = psTBody->psChild; psCur;
     335         421 :                      psCur = psCur->psNext)
     336             :                 {
     337         421 :                     if (psCur->eType == CXT_Element &&
     338         421 :                         strcmp(psCur->pszValue, "tr") == 0)
     339             :                     {
     340         421 :                         const CPLXMLNode *psTd = CPLGetXMLNode(psCur, "td");
     341         421 :                         if (psTd)
     342             :                         {
     343             :                             const char *pszFieldName =
     344         313 :                                 CPLGetXMLValue(psTd, "itemprop", nullptr);
     345             :                             const char *pszValue =
     346         313 :                                 CPLGetXMLValue(psTd, nullptr, nullptr);
     347         313 :                             if (pszFieldName && pszValue)
     348             :                             {
     349         313 :                                 const auto eValType = CPLGetValueType(pszValue);
     350         313 :                                 OGRFieldType eType = OFTString;
     351         313 :                                 if (eValType == CPL_VALUE_INTEGER)
     352             :                                 {
     353             :                                     const GIntBig nVal =
     354          96 :                                         CPLAtoGIntBig(pszValue);
     355          96 :                                     if (nVal < INT_MIN || nVal > INT_MAX)
     356           1 :                                         eType = OFTInteger64;
     357             :                                     else
     358          95 :                                         eType = OFTInteger;
     359             :                                 }
     360         217 :                                 else if (eValType == CPL_VALUE_REAL)
     361          69 :                                     eType = OFTReal;
     362             :                                 else
     363             :                                 {
     364             :                                     int nYear, nMonth, nDay, nHour, nMin, nSec;
     365         148 :                                     if (sscanf(pszValue,
     366             :                                                "%04d/%02d/%02d %02d:%02d:%02d",
     367             :                                                &nYear, &nMonth, &nDay, &nHour,
     368         148 :                                                &nMin, &nSec) == 6)
     369             :                                     {
     370          49 :                                         eType = OFTDateTime;
     371             :                                     }
     372          99 :                                     else if (sscanf(pszValue, "%04d/%02d/%02d",
     373             :                                                     &nYear, &nMonth,
     374          99 :                                                     &nDay) == 3)
     375             :                                     {
     376          49 :                                         eType = OFTDate;
     377             :                                     }
     378          50 :                                     else if (sscanf(pszValue, "%02d:%02d:%02d",
     379          50 :                                                     &nHour, &nMin, &nSec) == 3)
     380             :                                     {
     381           1 :                                         eType = OFTTime;
     382             :                                     }
     383             :                                 }
     384         313 :                                 auto oIter = oMapFieldTypes.find(pszFieldName);
     385         313 :                                 if (oIter == oMapFieldTypes.end())
     386             :                                 {
     387          93 :                                     aosFieldNames.emplace_back(pszFieldName);
     388          93 :                                     oMapFieldTypes[pszFieldName] = eType;
     389             :                                 }
     390         220 :                                 else if (oIter->second != eType)
     391             :                                 {
     392           6 :                                     const auto eOldType = oIter->second;
     393           6 :                                     if (eType == OFTInteger64 &&
     394             :                                         eOldType == OFTInteger)
     395             :                                     {
     396           1 :                                         oIter->second = OFTInteger64;
     397             :                                     }
     398           5 :                                     else if (eType == OFTReal &&
     399           0 :                                              (eOldType == OFTInteger ||
     400             :                                               eOldType == OFTInteger64))
     401             :                                     {
     402           1 :                                         oIter->second = OFTReal;
     403             :                                     }
     404           4 :                                     else if ((eType == OFTInteger ||
     405           3 :                                               eType == OFTInteger64) &&
     406           2 :                                              (eOldType == OFTInteger64 ||
     407             :                                               eOldType == OFTReal))
     408             :                                     {
     409             :                                         // do nothing
     410             :                                     }
     411             :                                     else
     412             :                                     {
     413           2 :                                         oIter->second = OFTString;
     414             :                                     }
     415             :                                 }
     416             :                             }
     417             :                         }
     418             :                     }
     419             :                 }
     420             :             }
     421             :         }
     422         251 :         m_psCurNode = m_psCurNode->psNext;
     423             :     }
     424             : 
     425          47 :     m_poFeatureDefn->SetGeomType(eLayerGType);
     426         140 :     for (const auto &osFieldName : aosFieldNames)
     427             :     {
     428         186 :         OGRFieldDefn oField(osFieldName.c_str(), oMapFieldTypes[osFieldName]);
     429          93 :         m_poFeatureDefn->AddFieldDefn(&oField);
     430             :     }
     431             : 
     432          47 :     OGRMapMLReaderLayer::ResetReading();
     433          47 : }
     434             : 
     435             : /************************************************************************/
     436             : /*                        ~OGRMapMLReaderLayer()                        */
     437             : /************************************************************************/
     438             : 
     439          94 : OGRMapMLReaderLayer::~OGRMapMLReaderLayer()
     440             : {
     441          47 :     if (m_poSRS)
     442          46 :         m_poSRS->Release();
     443          47 :     m_poFeatureDefn->Release();
     444          94 : }
     445             : 
     446             : /************************************************************************/
     447             : /*                            TestCapability()                          */
     448             : /************************************************************************/
     449             : 
     450          38 : int OGRMapMLReaderLayer::TestCapability(const char *pszCap)
     451             : {
     452             : 
     453          38 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
     454          14 :         return true;
     455          24 :     return false;
     456             : }
     457             : 
     458             : /************************************************************************/
     459             : /*                              ResetReading()                          */
     460             : /************************************************************************/
     461             : 
     462         170 : void OGRMapMLReaderLayer::ResetReading()
     463             : {
     464         170 :     m_psCurNode = m_psBody->psChild;
     465         170 :     m_nFID++;
     466         170 : }
     467             : 
     468             : /************************************************************************/
     469             : /*                              ParseGeometry()                         */
     470             : /************************************************************************/
     471             : 
     472         424 : static OGRGeometry *ParseGeometry(const CPLXMLNode *psElement)
     473             : {
     474         424 :     if (EQUAL(psElement->pszValue, "point"))
     475             :     {
     476             :         const char *pszCoordinates =
     477           5 :             CPLGetXMLValue(psElement, "coordinates", nullptr);
     478           5 :         if (pszCoordinates)
     479             :         {
     480             :             const CPLStringList aosTokens(
     481           5 :                 CSLTokenizeString2(pszCoordinates, " ", 0));
     482           5 :             if (aosTokens.size() == 2)
     483             :             {
     484           5 :                 return new OGRPoint(CPLAtof(aosTokens[0]),
     485           5 :                                     CPLAtof(aosTokens[1]));
     486             :             }
     487             :         }
     488             :     }
     489             : 
     490         419 :     if (EQUAL(psElement->pszValue, "linestring"))
     491             :     {
     492             :         const char *pszCoordinates =
     493           1 :             CPLGetXMLValue(psElement, "coordinates", nullptr);
     494           1 :         if (pszCoordinates)
     495             :         {
     496             :             const CPLStringList aosTokens(
     497           1 :                 CSLTokenizeString2(pszCoordinates, " ", 0));
     498           1 :             if ((aosTokens.size() % 2) == 0)
     499             :             {
     500           1 :                 OGRLineString *poLS = new OGRLineString();
     501           1 :                 const int nNumPoints = aosTokens.size() / 2;
     502           1 :                 poLS->setNumPoints(nNumPoints);
     503           3 :                 for (int i = 0; i < nNumPoints; i++)
     504             :                 {
     505           2 :                     poLS->setPoint(i, CPLAtof(aosTokens[2 * i]),
     506           2 :                                    CPLAtof(aosTokens[2 * i + 1]));
     507             :                 }
     508           1 :                 return poLS;
     509             :             }
     510             :         }
     511             :     }
     512             : 
     513         418 :     if (EQUAL(psElement->pszValue, "polygon"))
     514             :     {
     515         413 :         OGRPolygon *poPolygon = new OGRPolygon();
     516         827 :         for (const CPLXMLNode *psCur = psElement->psChild; psCur;
     517         414 :              psCur = psCur->psNext)
     518             :         {
     519         414 :             if (psCur->eType == CXT_Element &&
     520         414 :                 strcmp(psCur->pszValue, "coordinates") == 0 && psCur->psChild &&
     521         414 :                 psCur->psChild->eType == CXT_Text)
     522             :             {
     523             :                 const CPLStringList aosTokens(
     524         828 :                     CSLTokenizeString2(psCur->psChild->pszValue, " ", 0));
     525         414 :                 if ((aosTokens.size() % 2) == 0)
     526             :                 {
     527         414 :                     OGRLinearRing *poLS = new OGRLinearRing();
     528         414 :                     const int nNumPoints = aosTokens.size() / 2;
     529         414 :                     poLS->setNumPoints(nNumPoints);
     530       10317 :                     for (int i = 0; i < nNumPoints; i++)
     531             :                     {
     532        9903 :                         poLS->setPoint(i, CPLAtof(aosTokens[2 * i]),
     533        9903 :                                        CPLAtof(aosTokens[2 * i + 1]));
     534             :                     }
     535         414 :                     poPolygon->addRingDirectly(poLS);
     536             :                 }
     537             :             }
     538             :         }
     539         413 :         return poPolygon;
     540             :     }
     541             : 
     542           5 :     if (EQUAL(psElement->pszValue, "multipoint"))
     543             :     {
     544             :         const char *pszCoordinates =
     545           1 :             CPLGetXMLValue(psElement, "coordinates", nullptr);
     546           1 :         if (pszCoordinates)
     547             :         {
     548             :             const CPLStringList aosTokens(
     549           1 :                 CSLTokenizeString2(pszCoordinates, " ", 0));
     550           1 :             if ((aosTokens.size() % 2) == 0)
     551             :             {
     552           1 :                 OGRMultiPoint *poMLP = new OGRMultiPoint();
     553           1 :                 const int nNumPoints = aosTokens.size() / 2;
     554           3 :                 for (int i = 0; i < nNumPoints; i++)
     555             :                 {
     556           2 :                     poMLP->addGeometryDirectly(
     557           2 :                         new OGRPoint(CPLAtof(aosTokens[2 * i]),
     558           4 :                                      CPLAtof(aosTokens[2 * i + 1])));
     559             :                 }
     560           1 :                 return poMLP;
     561             :             }
     562             :         }
     563             :     }
     564             : 
     565           4 :     if (EQUAL(psElement->pszValue, "multilinestring"))
     566             :     {
     567           1 :         OGRMultiLineString *poMLS = new OGRMultiLineString();
     568           3 :         for (const CPLXMLNode *psCur = psElement->psChild; psCur;
     569           2 :              psCur = psCur->psNext)
     570             :         {
     571           2 :             if (psCur->eType == CXT_Element &&
     572           2 :                 strcmp(psCur->pszValue, "coordinates") == 0 && psCur->psChild &&
     573           2 :                 psCur->psChild->eType == CXT_Text)
     574             :             {
     575             :                 const CPLStringList aosTokens(
     576           4 :                     CSLTokenizeString2(psCur->psChild->pszValue, " ", 0));
     577           2 :                 if ((aosTokens.size() % 2) == 0)
     578             :                 {
     579           2 :                     OGRLineString *poLS = new OGRLineString();
     580           2 :                     const int nNumPoints = aosTokens.size() / 2;
     581           2 :                     poLS->setNumPoints(nNumPoints);
     582           6 :                     for (int i = 0; i < nNumPoints; i++)
     583             :                     {
     584           4 :                         poLS->setPoint(i, CPLAtof(aosTokens[2 * i]),
     585           4 :                                        CPLAtof(aosTokens[2 * i + 1]));
     586             :                     }
     587           2 :                     poMLS->addGeometryDirectly(poLS);
     588             :                 }
     589             :             }
     590             :         }
     591           1 :         return poMLS;
     592             :     }
     593             : 
     594           3 :     if (EQUAL(psElement->pszValue, "multipolygon"))
     595             :     {
     596           1 :         OGRMultiPolygon *poMLP = new OGRMultiPolygon();
     597           3 :         for (const CPLXMLNode *psCur = psElement->psChild; psCur;
     598           2 :              psCur = psCur->psNext)
     599             :         {
     600           2 :             if (psCur->eType == CXT_Element &&
     601           2 :                 EQUAL(psCur->pszValue, "polygon"))
     602             :             {
     603           2 :                 OGRGeometry *poSubGeom = ParseGeometry(psCur);
     604           2 :                 if (poSubGeom)
     605           2 :                     poMLP->addGeometryDirectly(poSubGeom);
     606             :             }
     607             :         }
     608           1 :         return poMLP;
     609             :     }
     610             : 
     611           2 :     if (EQUAL(psElement->pszValue, "geometrycollection"))
     612             :     {
     613           1 :         OGRGeometryCollection *poGC = new OGRGeometryCollection();
     614           3 :         for (const CPLXMLNode *psCur = psElement->psChild; psCur;
     615           2 :              psCur = psCur->psNext)
     616             :         {
     617           2 :             if (psCur->eType == CXT_Element &&
     618           2 :                 !EQUAL(psCur->pszValue, "geometrycollection"))
     619             :             {
     620           2 :                 OGRGeometry *poSubGeom = ParseGeometry(psCur);
     621           2 :                 if (poSubGeom)
     622           2 :                     poGC->addGeometryDirectly(poSubGeom);
     623             :             }
     624             :         }
     625           1 :         return poGC;
     626             :     }
     627             : 
     628           1 :     return nullptr;
     629             : }
     630             : 
     631             : /************************************************************************/
     632             : /*                            GetNextRawFeature()                       */
     633             : /************************************************************************/
     634             : 
     635         531 : OGRFeature *OGRMapMLReaderLayer::GetNextRawFeature()
     636             : {
     637         531 :     while (m_psCurNode != nullptr)
     638             :     {
     639        1467 :         if (m_psCurNode->eType == CXT_Element &&
     640         917 :             strcmp(m_psCurNode->pszValue, "feature") == 0 &&
     641         428 :             strcmp(CPLGetXMLValue(m_psCurNode, "class",
     642         428 :                                   m_poDS->m_osDefaultLayerName.c_str()),
     643         428 :                    m_poFeatureDefn->GetName()) == 0)
     644             :         {
     645         425 :             break;
     646             :         }
     647          64 :         m_psCurNode = m_psCurNode->psNext;
     648             :     }
     649         467 :     if (m_psCurNode == nullptr)
     650          42 :         return nullptr;
     651             : 
     652         425 :     OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
     653         425 :     poFeature->SetFID(m_nFID);
     654         425 :     const char *pszId = CPLGetXMLValue(m_psCurNode, "id", nullptr);
     655         848 :     if (pszId &&
     656         848 :         STARTS_WITH_CI(pszId,
     657             :                        (CPLString(m_poFeatureDefn->GetName()) + '.').c_str()))
     658             :     {
     659         423 :         poFeature->SetFID(
     660         423 :             CPLAtoGIntBig(pszId + strlen(m_poFeatureDefn->GetName()) + 1));
     661             :     }
     662         425 :     m_nFID++;
     663             : 
     664         425 :     const CPLXMLNode *psGeometry = CPLGetXMLNode(m_psCurNode, "geometry");
     665         425 :     if (psGeometry && psGeometry->psChild &&
     666         420 :         psGeometry->psChild->eType == CXT_Element)
     667             :     {
     668         420 :         OGRGeometry *poGeom = ParseGeometry(psGeometry->psChild);
     669         420 :         if (poGeom)
     670             :         {
     671         419 :             poGeom->assignSpatialReference(GetSpatialRef());
     672         419 :             poFeature->SetGeometryDirectly(poGeom);
     673             :         }
     674             :     }
     675             : 
     676             :     const CPLXMLNode *psTBody =
     677         425 :         CPLGetXMLNode(m_psCurNode, "properties.div.table.tbody");
     678         425 :     if (psTBody)
     679             :     {
     680        2079 :         for (const CPLXMLNode *psCur = psTBody->psChild; psCur;
     681        1661 :              psCur = psCur->psNext)
     682             :         {
     683        1661 :             if (psCur->eType == CXT_Element &&
     684        1661 :                 strcmp(psCur->pszValue, "tr") == 0)
     685             :             {
     686        1661 :                 const CPLXMLNode *psTd = CPLGetXMLNode(psCur, "td");
     687        1661 :                 if (psTd)
     688             :                 {
     689             :                     const char *pszFieldName =
     690        1243 :                         CPLGetXMLValue(psTd, "itemprop", nullptr);
     691             :                     const char *pszValue =
     692        1243 :                         CPLGetXMLValue(psTd, nullptr, nullptr);
     693        1243 :                     if (pszFieldName && pszValue)
     694             :                     {
     695        1243 :                         poFeature->SetField(pszFieldName, pszValue);
     696             :                     }
     697             :                 }
     698             :             }
     699             :         }
     700             :     }
     701             : 
     702         425 :     m_psCurNode = m_psCurNode->psNext;
     703             : 
     704         425 :     return poFeature;
     705             : }
     706             : 
     707             : /************************************************************************/
     708             : /*                         OGRMapMLWriterDataset()                      */
     709             : /************************************************************************/
     710             : 
     711          47 : OGRMapMLWriterDataset::OGRMapMLWriterDataset(VSILFILE *fpOut) : m_fpOut(fpOut)
     712             : {
     713          47 : }
     714             : 
     715             : /************************************************************************/
     716             : /*                        ~OGRMapMLWriterDataset()                      */
     717             : /************************************************************************/
     718             : 
     719          94 : OGRMapMLWriterDataset::~OGRMapMLWriterDataset()
     720             : {
     721          47 :     if (m_fpOut)
     722             :     {
     723          47 :         if (!m_osExtentUnits.empty())
     724          46 :             CPLAddXMLAttributeAndValue(m_psExtent, "units", m_osExtentUnits);
     725             : 
     726         117 :         const auto addMinMax = [](CPLXMLNode *psNode, const char *pszRadix,
     727             :                                   const CPLStringList &aosList)
     728             :         {
     729             :             const char *pszValue =
     730         117 :                 aosList.FetchNameValue((CPLString(pszRadix) + "_MIN").c_str());
     731         117 :             if (pszValue)
     732             :             {
     733           5 :                 CPLAddXMLAttributeAndValue(psNode, "min", pszValue);
     734             :             }
     735             :             pszValue =
     736         117 :                 aosList.FetchNameValue((CPLString(pszRadix) + "_MAX").c_str());
     737         117 :             if (pszValue)
     738             :             {
     739           5 :                 CPLAddXMLAttributeAndValue(psNode, "max", pszValue);
     740             :             }
     741         117 :         };
     742             : 
     743          47 :         if (m_sExtent.IsInit())
     744             :         {
     745          29 :             const char *pszUnits = m_oSRS.IsProjected() ? "pcrs" : "gcrs";
     746          29 :             const char *pszXAxis = m_oSRS.IsProjected() ? "x" : "longitude";
     747          29 :             const char *pszYAxis = m_oSRS.IsProjected() ? "y" : "latitude";
     748             : 
     749             :             CPLXMLNode *psXmin =
     750          29 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     751          29 :             CPLAddXMLAttributeAndValue(psXmin, "name", "xmin");
     752          29 :             CPLAddXMLAttributeAndValue(psXmin, "type", "location");
     753          29 :             CPLAddXMLAttributeAndValue(psXmin, "units", pszUnits);
     754          29 :             CPLAddXMLAttributeAndValue(psXmin, "axis", pszXAxis);
     755          29 :             CPLAddXMLAttributeAndValue(psXmin, "position", "top-left");
     756          29 :             CPLAddXMLAttributeAndValue(
     757             :                 psXmin, "value",
     758             :                 m_aosOptions.FetchNameValueDef(
     759             :                     "EXTENT_XMIN", CPLSPrintf("%.8f", m_sExtent.MinX)));
     760          29 :             addMinMax(psXmin, "EXTENT_XMIN", m_aosOptions);
     761             : 
     762             :             CPLXMLNode *psYmin =
     763          29 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     764          29 :             CPLAddXMLAttributeAndValue(psYmin, "name", "ymin");
     765          29 :             CPLAddXMLAttributeAndValue(psYmin, "type", "location");
     766          29 :             CPLAddXMLAttributeAndValue(psYmin, "units", pszUnits);
     767          29 :             CPLAddXMLAttributeAndValue(psYmin, "axis", pszYAxis);
     768          29 :             CPLAddXMLAttributeAndValue(psYmin, "position", "bottom-right");
     769          29 :             CPLAddXMLAttributeAndValue(
     770             :                 psYmin, "value",
     771             :                 m_aosOptions.FetchNameValueDef(
     772             :                     "EXTENT_YMIN", CPLSPrintf("%.8f", m_sExtent.MinY)));
     773          29 :             addMinMax(psYmin, "EXTENT_YMIN", m_aosOptions);
     774             : 
     775             :             CPLXMLNode *psXmax =
     776          29 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     777          29 :             CPLAddXMLAttributeAndValue(psXmax, "name", "xmax");
     778          29 :             CPLAddXMLAttributeAndValue(psXmax, "type", "location");
     779          29 :             CPLAddXMLAttributeAndValue(psXmax, "units", pszUnits);
     780          29 :             CPLAddXMLAttributeAndValue(psXmax, "axis", pszXAxis);
     781          29 :             CPLAddXMLAttributeAndValue(psXmax, "position", "bottom-right");
     782          29 :             CPLAddXMLAttributeAndValue(
     783             :                 psXmax, "value",
     784             :                 m_aosOptions.FetchNameValueDef(
     785             :                     "EXTENT_XMAX", CPLSPrintf("%.8f", m_sExtent.MaxX)));
     786          29 :             addMinMax(psXmax, "EXTENT_XMAX", m_aosOptions);
     787             : 
     788             :             CPLXMLNode *psYmax =
     789          29 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     790          29 :             CPLAddXMLAttributeAndValue(psYmax, "name", "ymax");
     791          29 :             CPLAddXMLAttributeAndValue(psYmax, "type", "location");
     792          29 :             CPLAddXMLAttributeAndValue(psYmax, "units", pszUnits);
     793          29 :             CPLAddXMLAttributeAndValue(psYmax, "axis", pszYAxis);
     794          29 :             CPLAddXMLAttributeAndValue(psYmax, "position", "top-left");
     795          29 :             CPLAddXMLAttributeAndValue(
     796             :                 psYmax, "value",
     797             :                 m_aosOptions.FetchNameValueDef(
     798             :                     "EXTENT_YMAX", CPLSPrintf("%.8f", m_sExtent.MaxY)));
     799          29 :             addMinMax(psYmax, "EXTENT_YMAX", m_aosOptions);
     800             :         }
     801             : 
     802          47 :         if (!m_osExtentUnits.empty())
     803             :         {
     804             :             CPLXMLNode *psInput =
     805          46 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     806          46 :             CPLAddXMLAttributeAndValue(psInput, "name", "projection");
     807          46 :             CPLAddXMLAttributeAndValue(psInput, "type", "hidden");
     808          46 :             CPLAddXMLAttributeAndValue(psInput, "value", m_osExtentUnits);
     809             :         }
     810             : 
     811          47 :         const char *pszZoom = m_aosOptions.FetchNameValue("EXTENT_ZOOM");
     812          47 :         if (pszZoom)
     813             :         {
     814             :             CPLXMLNode *psInput =
     815           1 :                 CPLCreateXMLNode(m_psExtent, CXT_Element, "input");
     816           1 :             CPLAddXMLAttributeAndValue(psInput, "name", "zoom");
     817           1 :             CPLAddXMLAttributeAndValue(psInput, "type", "zoom");
     818           1 :             CPLAddXMLAttributeAndValue(psInput, "value", pszZoom);
     819           1 :             addMinMax(psInput, "EXTENT_ZOOM", m_aosOptions);
     820             :         }
     821             : 
     822             :         const char *pszExtentExtra =
     823          47 :             m_aosOptions.FetchNameValue("EXTENT_EXTRA");
     824          47 :         if (pszExtentExtra)
     825             :         {
     826           1 :             CPLXMLNode *psExtra = pszExtentExtra[0] == '<'
     827           1 :                                       ? CPLParseXMLString(pszExtentExtra)
     828           0 :                                       : CPLParseXMLFile(pszExtentExtra);
     829           1 :             if (psExtra)
     830             :             {
     831           1 :                 CPLXMLNode *psLastChild = m_psExtent->psChild;
     832           1 :                 if (psLastChild == nullptr)
     833           0 :                     m_psExtent->psChild = psExtra;
     834             :                 else
     835             :                 {
     836           8 :                     while (psLastChild->psNext)
     837           7 :                         psLastChild = psLastChild->psNext;
     838           1 :                     psLastChild->psNext = psExtra;
     839             :                 }
     840             :             }
     841             :         }
     842             : 
     843          47 :         char *pszDoc = CPLSerializeXMLTree(m_psRoot);
     844          47 :         const size_t nSize = strlen(pszDoc);
     845          47 :         if (VSIFWriteL(pszDoc, 1, nSize, m_fpOut) != nSize)
     846             :         {
     847           0 :             CPLError(CE_Failure, CPLE_FileIO,
     848             :                      "Failed to write whole XML document");
     849             :         }
     850          47 :         VSIFCloseL(m_fpOut);
     851          47 :         VSIFree(pszDoc);
     852             :     }
     853          47 :     CPLDestroyXMLNode(m_psRoot);
     854          94 : }
     855             : 
     856             : /************************************************************************/
     857             : /*                              Create()                                */
     858             : /************************************************************************/
     859             : 
     860          49 : GDALDataset *OGRMapMLWriterDataset::Create(const char *pszFilename, int nXSize,
     861             :                                            int nYSize, int nBandsIn,
     862             :                                            GDALDataType eDT,
     863             :                                            char **papszOptions)
     864             : {
     865          49 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0 || eDT != GDT_Unknown)
     866             :     {
     867           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     868             :                  "Only vector creation supported");
     869           0 :         return nullptr;
     870             :     }
     871          49 :     VSILFILE *fpOut = VSIFOpenL(pszFilename, "wb");
     872          49 :     if (fpOut == nullptr)
     873             :     {
     874           2 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
     875           2 :         return nullptr;
     876             :     }
     877          47 :     auto poDS = new OGRMapMLWriterDataset(fpOut);
     878             : 
     879          47 :     poDS->m_psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "mapml");
     880          47 :     CPLXMLNode *psHead = CPLCreateXMLNode(poDS->m_psRoot, CXT_Element, "head");
     881             : 
     882          47 :     const char *pszHead = CSLFetchNameValue(papszOptions, "HEAD");
     883          47 :     if (pszHead)
     884             :     {
     885           1 :         CPLXMLNode *psHeadUser = pszHead[0] == '<' ? CPLParseXMLString(pszHead)
     886           0 :                                                    : CPLParseXMLFile(pszHead);
     887           1 :         if (psHeadUser)
     888             :         {
     889           1 :             if (psHeadUser->eType == CXT_Element &&
     890           1 :                 strcmp(psHeadUser->pszValue, "head") == 0)
     891             :             {
     892           0 :                 psHead->psChild = psHeadUser->psChild;
     893           0 :                 psHeadUser->psChild = nullptr;
     894             :             }
     895           1 :             else if (psHeadUser->eType == CXT_Element)
     896             :             {
     897           1 :                 psHead->psChild = psHeadUser;
     898           1 :                 psHeadUser = nullptr;
     899             :             }
     900           1 :             CPLDestroyXMLNode(psHeadUser);
     901             :         }
     902             :     }
     903             : 
     904             :     const CPLString osExtentUnits =
     905          94 :         CSLFetchNameValueDef(papszOptions, "EXTENT_UNITS", "");
     906          47 :     if (!osExtentUnits.empty() && osExtentUnits != "AUTO")
     907             :     {
     908           2 :         int nTargetEPSGCode = 0;
     909           9 :         for (const auto &knownCRS : asKnownCRS)
     910             :         {
     911           8 :             if (osExtentUnits == knownCRS.pszName)
     912             :             {
     913           1 :                 poDS->m_osExtentUnits = knownCRS.pszName;
     914           1 :                 nTargetEPSGCode = knownCRS.nEPSGCode;
     915           1 :                 break;
     916             :             }
     917             :         }
     918           2 :         if (nTargetEPSGCode == 0)
     919             :         {
     920           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     921             :                      "Unsupported value for EXTENT_UNITS");
     922           1 :             delete poDS;
     923           1 :             return nullptr;
     924             :         }
     925           1 :         poDS->m_oSRS.importFromEPSG(nTargetEPSGCode);
     926           1 :         poDS->m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     927             :     }
     928             : 
     929          46 :     CPLXMLNode *psBody = CPLCreateXMLNode(poDS->m_psRoot, CXT_Element, "body");
     930          46 :     poDS->m_psExtent = CPLCreateXMLNode(psBody, CXT_Element, "extent");
     931             :     const char *pszExtentAction =
     932          46 :         CSLFetchNameValue(papszOptions, "EXTENT_ACTION");
     933          46 :     if (pszExtentAction)
     934           1 :         CPLAddXMLAttributeAndValue(poDS->m_psExtent, "action", pszExtentAction);
     935             : 
     936          46 :     poDS->m_psLastChild = poDS->m_psExtent;
     937             : 
     938          46 :     const char *pszBodyLinks = CSLFetchNameValue(papszOptions, "BODY_LINKS");
     939          46 :     if (pszBodyLinks)
     940             :     {
     941           2 :         CPLXMLNode *psLinks = CPLParseXMLString(pszBodyLinks);
     942           2 :         if (psLinks)
     943             :         {
     944           2 :             poDS->m_psExtent->psNext = psLinks;
     945           2 :             poDS->m_psLastChild = psLinks;
     946           3 :             while (poDS->m_psLastChild->psNext)
     947           1 :                 poDS->m_psLastChild = poDS->m_psLastChild->psNext;
     948             :         }
     949             :     }
     950             : 
     951          46 :     poDS->m_aosOptions = CSLDuplicate(papszOptions);
     952             : 
     953          46 :     return poDS;
     954             : }
     955             : 
     956             : /************************************************************************/
     957             : /*                             GetLayer()                               */
     958             : /************************************************************************/
     959             : 
     960           3 : OGRLayer *OGRMapMLWriterDataset::GetLayer(int idx)
     961             : {
     962           3 :     return idx >= 0 && idx < GetLayerCount() ? m_apoLayers[idx].get() : nullptr;
     963             : }
     964             : 
     965             : /************************************************************************/
     966             : /*                            TestCapability()                          */
     967             : /************************************************************************/
     968             : 
     969          50 : int OGRMapMLWriterDataset::TestCapability(const char *pszCap)
     970             : {
     971          50 :     if (EQUAL(pszCap, ODsCCreateLayer))
     972          33 :         return true;
     973          17 :     return false;
     974             : }
     975             : 
     976             : /************************************************************************/
     977             : /*                           ICreateLayer()                             */
     978             : /************************************************************************/
     979             : 
     980             : OGRLayer *
     981          63 : OGRMapMLWriterDataset::ICreateLayer(const char *pszLayerName,
     982             :                                     const OGRGeomFieldDefn *poGeomFieldDefn,
     983             :                                     CSLConstList /*papszOptions*/)
     984             : {
     985         126 :     OGRSpatialReference oSRS_WGS84;
     986             :     const auto poSRSIn =
     987          63 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
     988          63 :     const OGRSpatialReference *poSRS = poSRSIn;
     989          63 :     if (poSRS == nullptr)
     990             :     {
     991          61 :         oSRS_WGS84.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
     992          61 :         oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     993          61 :         poSRS = &oSRS_WGS84;
     994             :     }
     995             : 
     996          63 :     if (m_oSRS.IsEmpty())
     997             :     {
     998          45 :         const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
     999          45 :         const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
    1000          45 :         if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    1001             :         {
    1002          45 :             const int nEPSGCode = atoi(pszAuthCode);
    1003          52 :             for (const auto &knownCRS : asKnownCRS)
    1004             :             {
    1005          51 :                 if (nEPSGCode == knownCRS.nEPSGCode)
    1006             :                 {
    1007          44 :                     m_osExtentUnits = knownCRS.pszName;
    1008          44 :                     m_oSRS.importFromEPSG(nEPSGCode);
    1009          44 :                     break;
    1010             :                 }
    1011             :             }
    1012             :         }
    1013          45 :         if (m_oSRS.IsEmpty())
    1014             :         {
    1015           1 :             m_osExtentUnits = "WGS84";
    1016           1 :             m_oSRS.importFromEPSG(EPSG_CODE_WGS84);
    1017             :         }
    1018          45 :         m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1019             :     }
    1020          63 :     m_pszFormatCoordTuple = m_oSRS.IsGeographic() ? "%.8f %.8f" : "%.2f %.2f";
    1021             : 
    1022             :     auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    1023         126 :         OGRCreateCoordinateTransformation(poSRS, &m_oSRS));
    1024          63 :     if (!poCT)
    1025           0 :         return nullptr;
    1026             : 
    1027             :     OGRMapMLWriterLayer *poLayer =
    1028          63 :         new OGRMapMLWriterLayer(this, pszLayerName, std::move(poCT));
    1029             : 
    1030          63 :     m_apoLayers.push_back(std::unique_ptr<OGRMapMLWriterLayer>(poLayer));
    1031          63 :     return m_apoLayers.back().get();
    1032             : }
    1033             : 
    1034             : /************************************************************************/
    1035             : /*                            AddFeature()                              */
    1036             : /************************************************************************/
    1037             : 
    1038         103 : void OGRMapMLWriterDataset::AddFeature(CPLXMLNode *psNode)
    1039             : {
    1040         103 :     m_psLastChild->psNext = psNode;
    1041         103 :     m_psLastChild = psNode;
    1042         103 : }
    1043             : 
    1044             : /************************************************************************/
    1045             : /*                         OGRMapMLWriterLayer()                        */
    1046             : /************************************************************************/
    1047             : 
    1048          63 : OGRMapMLWriterLayer::OGRMapMLWriterLayer(
    1049             :     OGRMapMLWriterDataset *poDS, const char *pszLayerName,
    1050          63 :     std::unique_ptr<OGRCoordinateTransformation> &&poCT)
    1051          63 :     : m_poDS(poDS), m_poCT(std::move(poCT))
    1052             : {
    1053          63 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    1054          63 :     m_poFeatureDefn->Reference();
    1055          63 : }
    1056             : 
    1057             : /************************************************************************/
    1058             : /*                        ~OGRMapMLWriterLayer()                        */
    1059             : /************************************************************************/
    1060             : 
    1061         126 : OGRMapMLWriterLayer::~OGRMapMLWriterLayer()
    1062             : {
    1063          63 :     m_poFeatureDefn->Release();
    1064         126 : }
    1065             : 
    1066             : /************************************************************************/
    1067             : /*                            TestCapability()                          */
    1068             : /************************************************************************/
    1069             : 
    1070         127 : int OGRMapMLWriterLayer::TestCapability(const char *pszCap)
    1071             : {
    1072             : 
    1073         127 :     if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCCreateField))
    1074          32 :         return true;
    1075          95 :     return false;
    1076             : }
    1077             : 
    1078             : /************************************************************************/
    1079             : /*                            CreateField()                             */
    1080             : /************************************************************************/
    1081             : 
    1082         103 : OGRErr OGRMapMLWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn, int)
    1083             : {
    1084         103 :     m_poFeatureDefn->AddFieldDefn(poFieldDefn);
    1085         103 :     return OGRERR_NONE;
    1086             : }
    1087             : 
    1088             : /************************************************************************/
    1089             : /*                   writeLineStringCoordinates()                       */
    1090             : /************************************************************************/
    1091             : 
    1092          25 : void OGRMapMLWriterLayer::writeLineStringCoordinates(CPLXMLNode *psContainer,
    1093             :                                                      const OGRLineString *poLS)
    1094             : {
    1095             :     CPLXMLNode *psCoordinates =
    1096          25 :         CPLCreateXMLNode(psContainer, CXT_Element, "coordinates");
    1097          50 :     std::string osCoordinates;
    1098          75 :     for (int i = 0; i < poLS->getNumPoints(); i++)
    1099             :     {
    1100          50 :         if (!osCoordinates.empty())
    1101          25 :             osCoordinates += ' ';
    1102          50 :         osCoordinates += CPLSPrintf(m_poDS->m_pszFormatCoordTuple,
    1103          50 :                                     poLS->getX(i), poLS->getY(i));
    1104             :     }
    1105          25 :     CPLCreateXMLNode(psCoordinates, CXT_Text, osCoordinates.c_str());
    1106          25 : }
    1107             : 
    1108             : /************************************************************************/
    1109             : /*                           writePolygon()                             */
    1110             : /************************************************************************/
    1111             : 
    1112          23 : void OGRMapMLWriterLayer::writePolygon(CPLXMLNode *psContainer,
    1113             :                                        const OGRPolygon *poPoly)
    1114             : {
    1115             :     CPLXMLNode *psPolygon =
    1116          23 :         CPLCreateXMLNode(psContainer, CXT_Element, "polygon");
    1117          23 :     bool bFirstRing = true;
    1118          47 :     for (const auto poRing : *poPoly)
    1119             :     {
    1120             :         const bool bReversePointOrder =
    1121          27 :             (bFirstRing && CPL_TO_BOOL(poRing->isClockwise())) ||
    1122           3 :             (!bFirstRing && !CPL_TO_BOOL(poRing->isClockwise()));
    1123          24 :         bFirstRing = false;
    1124             :         CPLXMLNode *psCoordinates =
    1125          24 :             CPLCreateXMLNode(psPolygon, CXT_Element, "coordinates");
    1126          48 :         std::string osCoordinates;
    1127          24 :         const int nPointCount = poRing->getNumPoints();
    1128         138 :         for (int i = 0; i < nPointCount; i++)
    1129             :         {
    1130         114 :             if (!osCoordinates.empty())
    1131          90 :                 osCoordinates += ' ';
    1132         114 :             const int idx = bReversePointOrder ? nPointCount - 1 - i : i;
    1133         114 :             osCoordinates += CPLSPrintf(m_poDS->m_pszFormatCoordTuple,
    1134         114 :                                         poRing->getX(idx), poRing->getY(idx));
    1135             :         }
    1136          24 :         CPLCreateXMLNode(psCoordinates, CXT_Text, osCoordinates.c_str());
    1137             :     }
    1138          23 : }
    1139             : 
    1140             : /************************************************************************/
    1141             : /*                          writeGeometry()                             */
    1142             : /************************************************************************/
    1143             : 
    1144          87 : void OGRMapMLWriterLayer::writeGeometry(CPLXMLNode *psContainer,
    1145             :                                         const OGRGeometry *poGeom,
    1146             :                                         bool bInGeometryCollection)
    1147             : {
    1148          87 :     switch (wkbFlatten(poGeom->getGeometryType()))
    1149             :     {
    1150          24 :         case wkbPoint:
    1151             :         {
    1152          24 :             const OGRPoint *poPoint = poGeom->toPoint();
    1153             :             CPLXMLNode *psPoint =
    1154          24 :                 CPLCreateXMLNode(psContainer, CXT_Element, "point");
    1155             :             CPLXMLNode *psCoordinates =
    1156          24 :                 CPLCreateXMLNode(psPoint, CXT_Element, "coordinates");
    1157          48 :             CPLCreateXMLNode(psCoordinates, CXT_Text,
    1158          24 :                              CPLSPrintf(m_poDS->m_pszFormatCoordTuple,
    1159             :                                         poPoint->getX(), poPoint->getY()));
    1160          24 :             break;
    1161             :         }
    1162             : 
    1163          16 :         case wkbLineString:
    1164             :         {
    1165          16 :             const OGRLineString *poLS = poGeom->toLineString();
    1166             :             CPLXMLNode *psLS =
    1167          16 :                 CPLCreateXMLNode(psContainer, CXT_Element, "linestring");
    1168          16 :             writeLineStringCoordinates(psLS, poLS);
    1169          16 :             break;
    1170             :         }
    1171             : 
    1172          14 :         case wkbPolygon:
    1173             :         {
    1174          14 :             const OGRPolygon *poPoly = poGeom->toPolygon();
    1175          14 :             writePolygon(psContainer, poPoly);
    1176          14 :             break;
    1177             :         }
    1178             : 
    1179           8 :         case wkbMultiPoint:
    1180             :         {
    1181           8 :             const OGRMultiPoint *poMP = poGeom->toMultiPoint();
    1182             :             CPLXMLNode *psMultiPoint =
    1183           8 :                 CPLCreateXMLNode(psContainer, CXT_Element, "multipoint");
    1184             :             CPLXMLNode *psCoordinates =
    1185           8 :                 CPLCreateXMLNode(psMultiPoint, CXT_Element, "coordinates");
    1186          16 :             std::string osCoordinates;
    1187          17 :             for (const auto poPoint : *poMP)
    1188             :             {
    1189           9 :                 if (!poPoint->IsEmpty())
    1190             :                 {
    1191           9 :                     if (!osCoordinates.empty())
    1192           1 :                         osCoordinates += ' ';
    1193             :                     osCoordinates +=
    1194           9 :                         CPLSPrintf(m_poDS->m_pszFormatCoordTuple,
    1195           9 :                                    poPoint->getX(), poPoint->getY());
    1196             :                 }
    1197             :             }
    1198           8 :             CPLCreateXMLNode(psCoordinates, CXT_Text, osCoordinates.c_str());
    1199           8 :             break;
    1200             :         }
    1201             : 
    1202           8 :         case wkbMultiLineString:
    1203             :         {
    1204           8 :             const OGRMultiLineString *poMLS = poGeom->toMultiLineString();
    1205             :             CPLXMLNode *psMultiLineString =
    1206           8 :                 CPLCreateXMLNode(psContainer, CXT_Element, "multilinestring");
    1207          17 :             for (const auto poLS : *poMLS)
    1208             :             {
    1209           9 :                 if (!poLS->IsEmpty())
    1210             :                 {
    1211           9 :                     writeLineStringCoordinates(psMultiLineString, poLS);
    1212             :                 }
    1213             :             }
    1214           8 :             break;
    1215             :         }
    1216             : 
    1217           8 :         case wkbMultiPolygon:
    1218             :         {
    1219           8 :             const OGRMultiPolygon *poMLP = poGeom->toMultiPolygon();
    1220             :             CPLXMLNode *psMultiPolygon =
    1221           8 :                 CPLCreateXMLNode(psContainer, CXT_Element, "multipolygon");
    1222          17 :             for (const auto poPoly : *poMLP)
    1223             :             {
    1224           9 :                 if (!poPoly->IsEmpty())
    1225             :                 {
    1226           9 :                     writePolygon(psMultiPolygon, poPoly);
    1227             :                 }
    1228             :             }
    1229           8 :             break;
    1230             :         }
    1231             : 
    1232           9 :         case wkbGeometryCollection:
    1233             :         {
    1234           9 :             const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1235             :             CPLXMLNode *psGeometryCollection =
    1236             :                 bInGeometryCollection
    1237           9 :                     ? psContainer
    1238           8 :                     : CPLCreateXMLNode(psContainer, CXT_Element,
    1239           9 :                                        "geometrycollection");
    1240          31 :             for (const auto poSubGeom : *poGC)
    1241             :             {
    1242          22 :                 if (!poSubGeom->IsEmpty())
    1243             :                 {
    1244          22 :                     writeGeometry(psGeometryCollection, poSubGeom, true);
    1245             :                 }
    1246             :             }
    1247           9 :             break;
    1248             :         }
    1249             : 
    1250           0 :         default:
    1251           0 :             break;
    1252             :     }
    1253          87 : }
    1254             : 
    1255             : /************************************************************************/
    1256             : /*                            ICreateFeature()                          */
    1257             : /************************************************************************/
    1258             : 
    1259         103 : OGRErr OGRMapMLWriterLayer::ICreateFeature(OGRFeature *poFeature)
    1260             : {
    1261         103 :     CPLXMLNode *psFeature = CPLCreateXMLNode(nullptr, CXT_Element, "feature");
    1262         103 :     GIntBig nFID = poFeature->GetFID();
    1263         103 :     if (nFID < 0)
    1264             :     {
    1265         102 :         nFID = m_nFID;
    1266         102 :         m_nFID++;
    1267             :     }
    1268             :     const CPLString osFID(
    1269         103 :         CPLSPrintf("%s." CPL_FRMT_GIB, m_poFeatureDefn->GetName(), nFID));
    1270         103 :     CPLAddXMLAttributeAndValue(psFeature, "id", osFID.c_str());
    1271         103 :     CPLAddXMLAttributeAndValue(psFeature, "class", m_poFeatureDefn->GetName());
    1272             : 
    1273         103 :     const int nFieldCount = poFeature->GetFieldCount();
    1274         103 :     if (nFieldCount > 0)
    1275             :     {
    1276             :         CPLXMLNode *psProperties =
    1277          88 :             CPLCreateXMLNode(psFeature, CXT_Element, "properties");
    1278          88 :         CPLXMLNode *psDiv = CPLCreateXMLNode(psProperties, CXT_Element, "div");
    1279          88 :         CPLAddXMLAttributeAndValue(psDiv, "class", "table-container");
    1280          88 :         CPLAddXMLAttributeAndValue(psDiv, "aria-labelledby",
    1281         176 :                                    ("caption-" + osFID).c_str());
    1282          88 :         CPLXMLNode *psTable = CPLCreateXMLNode(psDiv, CXT_Element, "table");
    1283             :         CPLXMLNode *psCaption =
    1284          88 :             CPLCreateXMLNode(psTable, CXT_Element, "caption");
    1285          88 :         CPLAddXMLAttributeAndValue(psCaption, "id",
    1286         176 :                                    ("caption-" + osFID).c_str());
    1287          88 :         CPLCreateXMLNode(psCaption, CXT_Text, "Feature properties");
    1288          88 :         CPLXMLNode *psTBody = CPLCreateXMLNode(psTable, CXT_Element, "tbody");
    1289             :         {
    1290          88 :             CPLXMLNode *psTr = CPLCreateXMLNode(psTBody, CXT_Element, "tr");
    1291             :             {
    1292          88 :                 CPLXMLNode *psTh = CPLCreateXMLNode(psTr, CXT_Element, "th");
    1293          88 :                 CPLAddXMLAttributeAndValue(psTh, "role", "columnheader");
    1294          88 :                 CPLAddXMLAttributeAndValue(psTh, "scope", "col");
    1295          88 :                 CPLCreateXMLNode(psTh, CXT_Text, "Property name");
    1296             :             }
    1297             :             {
    1298          88 :                 CPLXMLNode *psTh = CPLCreateXMLNode(psTr, CXT_Element, "th");
    1299          88 :                 CPLAddXMLAttributeAndValue(psTh, "role", "columnheader");
    1300          88 :                 CPLAddXMLAttributeAndValue(psTh, "scope", "col");
    1301          88 :                 CPLCreateXMLNode(psTh, CXT_Text, "Property value");
    1302             :             }
    1303             :         }
    1304         480 :         for (int i = 0; i < nFieldCount; i++)
    1305             :         {
    1306         392 :             if (poFeature->IsFieldSetAndNotNull(i))
    1307             :             {
    1308         253 :                 const auto poFieldDefn = poFeature->GetFieldDefnRef(i);
    1309         253 :                 CPLXMLNode *psTr = CPLCreateXMLNode(psTBody, CXT_Element, "tr");
    1310             :                 {
    1311             :                     CPLXMLNode *psTh =
    1312         253 :                         CPLCreateXMLNode(psTr, CXT_Element, "th");
    1313         253 :                     CPLAddXMLAttributeAndValue(psTh, "scope", "row");
    1314         253 :                     CPLCreateXMLNode(psTh, CXT_Text, poFieldDefn->GetNameRef());
    1315             :                 }
    1316             :                 {
    1317             :                     CPLXMLNode *psTd =
    1318         253 :                         CPLCreateXMLNode(psTr, CXT_Element, "td");
    1319         253 :                     CPLAddXMLAttributeAndValue(psTd, "itemprop",
    1320             :                                                poFieldDefn->GetNameRef());
    1321         253 :                     CPLCreateXMLNode(psTd, CXT_Text,
    1322             :                                      poFeature->GetFieldAsString(i));
    1323             :                 }
    1324             :             }
    1325             :         }
    1326             :     }
    1327             : 
    1328         103 :     const OGRGeometry *poGeom = poFeature->GetGeometryRef();
    1329         103 :     if (poGeom && !poGeom->IsEmpty())
    1330             :     {
    1331          65 :         OGRGeometry *poGeomClone = poGeom->clone();
    1332          65 :         if (poGeomClone->transform(m_poCT.get()) == OGRERR_NONE)
    1333             :         {
    1334             :             CPLXMLNode *psGeometry =
    1335          65 :                 CPLCreateXMLNode(nullptr, CXT_Element, "geometry");
    1336          65 :             writeGeometry(psGeometry, poGeomClone, false);
    1337          65 :             if (psGeometry->psChild == nullptr)
    1338             :             {
    1339           0 :                 CPLDestroyXMLNode(psGeometry);
    1340             :             }
    1341             :             else
    1342             :             {
    1343          65 :                 OGREnvelope sExtent;
    1344          65 :                 poGeomClone->getEnvelope(&sExtent);
    1345          65 :                 m_poDS->m_sExtent.Merge(sExtent);
    1346             : 
    1347          65 :                 CPLXMLNode *psLastChild = psFeature->psChild;
    1348         183 :                 while (psLastChild->psNext)
    1349         118 :                     psLastChild = psLastChild->psNext;
    1350          65 :                 psLastChild->psNext = psGeometry;
    1351             :             }
    1352             :         }
    1353          65 :         delete poGeomClone;
    1354             :     }
    1355             : 
    1356         103 :     m_poDS->AddFeature(psFeature);
    1357         206 :     return OGRERR_NONE;
    1358             : }
    1359             : 
    1360             : /************************************************************************/
    1361             : /*                         RegisterOGRMapML()                           */
    1362             : /************************************************************************/
    1363             : 
    1364        1595 : void RegisterOGRMapML()
    1365             : 
    1366             : {
    1367        1595 :     if (GDALGetDriverByName("MapML") != nullptr)
    1368         302 :         return;
    1369             : 
    1370        1293 :     GDALDriver *poDriver = new GDALDriver();
    1371             : 
    1372        1293 :     poDriver->SetDescription("MapML");
    1373        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    1374        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
    1375        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
    1376        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MapML");
    1377        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/mapml.html");
    1378        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1379        1293 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
    1380             : 
    1381        1293 :     poDriver->pfnIdentify = OGRMapMLReaderDataset::Identify;
    1382        1293 :     poDriver->pfnOpen = OGRMapMLReaderDataset::Open;
    1383        1293 :     poDriver->pfnCreate = OGRMapMLWriterDataset::Create;
    1384             : 
    1385        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
    1386             :                               "Integer Integer64 Real String "
    1387        1293 :                               "Date DateTime Time");
    1388             : 
    1389        1293 :     poDriver->SetMetadataItem(
    1390             :         GDAL_DMD_CREATIONOPTIONLIST,
    1391             :         "<CreationOptionList>"
    1392             :         "  <Option name='HEAD' type='string' "
    1393             :         "description='Filename or inline XML content for head element'/>"
    1394             :         "  <Option name='EXTENT_UNITS' type='string-select' description='Force "
    1395             :         "CRS'>"
    1396             :         "    <Value>AUTO</Value>"
    1397             :         "    <Value>WGS84</Value>"
    1398             :         "    <Value>OSMTILE</Value>"
    1399             :         "    <Value>CBMTILE</Value>"
    1400             :         "    <Value>APSTILE</Value>"
    1401             :         "  </Option>"
    1402             :         "  <Option name='EXTENT_ACTION' type='string' description='Value of "
    1403             :         "extent@action attribute'/>"
    1404             :         "  <Option name='EXTENT_XMIN' type='float' description='Override "
    1405             :         "extent xmin value'/>"
    1406             :         "  <Option name='EXTENT_YMIN' type='float' description='Override "
    1407             :         "extent ymin value'/>"
    1408             :         "  <Option name='EXTENT_XMAX' type='float' description='Override "
    1409             :         "extent xmax value'/>"
    1410             :         "  <Option name='EXTENT_YMAX' type='float' description='Override "
    1411             :         "extent ymax value'/>"
    1412             :         "  <Option name='EXTENT_XMIN_MIN' type='float' description='Min value "
    1413             :         "for extent.xmin value'/>"
    1414             :         "  <Option name='EXTENT_XMIN_MAX' type='float' description='Max value "
    1415             :         "for extent.xmin value'/>"
    1416             :         "  <Option name='EXTENT_YMIN_MIN' type='float' description='Min value "
    1417             :         "for extent.ymin value'/>"
    1418             :         "  <Option name='EXTENT_YMIN_MAX' type='float' description='Max value "
    1419             :         "for extent.ymin value'/>"
    1420             :         "  <Option name='EXTENT_XMAX_MIN' type='float' description='Min value "
    1421             :         "for extent.xmax value'/>"
    1422             :         "  <Option name='EXTENT_XMAX_MAX' type='float' description='Max value "
    1423             :         "for extent.xmax value'/>"
    1424             :         "  <Option name='EXTENT_YMAX_MIN' type='float' description='Min value "
    1425             :         "for extent.ymax value'/>"
    1426             :         "  <Option name='EXTENT_YMAX_MAX' type='float' description='Max value "
    1427             :         "for extent.ymax value'/>"
    1428             :         "  <Option name='EXTENT_ZOOM' type='int' description='Value of "
    1429             :         "extent.zoom'/>"
    1430             :         "  <Option name='EXTENT_ZOOM_MIN' type='int' description='Min value "
    1431             :         "for extent.zoom'/>"
    1432             :         "  <Option name='EXTENT_ZOOM_MAX' type='int' description='Max value "
    1433             :         "for extent.zoom'/>"
    1434             :         "  <Option name='EXTENT_EXTRA' type='string' "
    1435             :         "description='Filename of inline XML content for extra content to "
    1436             :         "insert in extent element'/>"
    1437             :         "  <Option name='BODY_LINKS' type='string' "
    1438             :         "description='Inline XML content for extra content to insert as link "
    1439             :         "elements in the body'/>"
    1440        1293 :         "</CreationOptionList>");
    1441             : 
    1442        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1443             : 
    1444        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1445             : }

Generated by: LCOV version 1.14