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

Generated by: LCOV version 1.14