Coverage Report

Created: 2025-08-11 09:23

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