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

Generated by: LCOV version 1.14