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

Generated by: LCOV version 1.14