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