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