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