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