Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OGR
4 : * Purpose: Implements OGRGMLDataSource class.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : *
13 : ******************************************************************************
14 : * Contributor: Alessandro Furieri, a.furieri@lqt.it
15 : * Portions of this module implementing GML_SKIP_RESOLVE_ELEMS HUGE
16 : * Developed for Faunalia ( http://www.faunalia.it) with funding from
17 : * Regione Toscana - Settore SISTEMA INFORMATIVO TERRITORIALE ED AMBIENTALE
18 : *
19 : ****************************************************************************/
20 :
21 : #include "cpl_port.h"
22 : #include "ogr_gml.h"
23 :
24 : #include <algorithm>
25 : #include <vector>
26 :
27 : #include "cpl_conv.h"
28 : #include "cpl_http.h"
29 : #include "cpl_string.h"
30 : #include "cpl_vsi_error.h"
31 : #include "gmlreaderp.h"
32 : #include "gmlregistry.h"
33 : #include "gmlutils.h"
34 : #include "ogr_p.h"
35 : #include "ogr_schema_override.h"
36 : #include "parsexsd.h"
37 : #include "../mem/ogr_mem.h"
38 :
39 : /************************************************************************/
40 : /* ReplaceSpaceByPct20IfNeeded() */
41 : /************************************************************************/
42 :
43 141 : static CPLString ReplaceSpaceByPct20IfNeeded(const char *pszURL)
44 : {
45 : // Replace ' ' by '%20'.
46 141 : CPLString osRet = pszURL;
47 141 : const char *pszNeedle = strstr(pszURL, "; ");
48 141 : if (pszNeedle)
49 : {
50 2 : char *pszTmp = static_cast<char *>(CPLMalloc(strlen(pszURL) + 2 + 1));
51 2 : const int nBeforeNeedle = static_cast<int>(pszNeedle - pszURL);
52 2 : memcpy(pszTmp, pszURL, nBeforeNeedle);
53 2 : strcpy(pszTmp + nBeforeNeedle, ";%20");
54 2 : strcpy(pszTmp + nBeforeNeedle + strlen(";%20"),
55 : pszNeedle + strlen("; "));
56 2 : osRet = pszTmp;
57 2 : CPLFree(pszTmp);
58 : }
59 :
60 141 : return osRet;
61 : }
62 :
63 : /************************************************************************/
64 : /* OGRGMLDataSource() */
65 : /************************************************************************/
66 :
67 535 : OGRGMLDataSource::OGRGMLDataSource()
68 : : papoLayers(nullptr), nLayers(0), papszCreateOptions(nullptr),
69 : fpOutput(nullptr), bFpOutputIsNonSeekable(false),
70 : bFpOutputSingleFile(false), bBBOX3D(false), nBoundedByLocation(-1),
71 : nSchemaInsertLocation(-1), bIsOutputGML3(false),
72 : bIsOutputGML3Deegree(false), bIsOutputGML32(false),
73 : eSRSNameFormat(SRSNAME_SHORT), bWriteSpaceIndentation(true),
74 : poReader(nullptr), bOutIsTempFile(false), bExposeGMLId(false),
75 : bExposeFid(false), bIsWFS(false), bUseGlobalSRSName(false),
76 : m_bInvertAxisOrderIfLatLong(false), m_bConsiderEPSGAsURN(false),
77 : m_eSwapCoordinates(GML_SWAP_AUTO), m_bGetSecondaryGeometryOption(false),
78 : eReadMode(STANDARD), poStoredGMLFeature(nullptr),
79 535 : poLastReadLayer(nullptr), bEmptyAsNull(true)
80 : {
81 535 : }
82 :
83 : /************************************************************************/
84 : /* ~OGRGMLDataSource() */
85 : /************************************************************************/
86 :
87 1070 : OGRGMLDataSource::~OGRGMLDataSource()
88 : {
89 535 : OGRGMLDataSource::Close();
90 1070 : }
91 :
92 : /************************************************************************/
93 : /* Close() */
94 : /************************************************************************/
95 :
96 1056 : CPLErr OGRGMLDataSource::Close()
97 : {
98 1056 : CPLErr eErr = CE_None;
99 1056 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
100 : {
101 1056 : if (fpOutput && !m_bWriteError)
102 : {
103 99 : if (nLayers == 0)
104 3 : WriteTopElements();
105 :
106 99 : const char *pszPrefix = GetAppPrefix();
107 99 : if (GMLFeatureCollection())
108 1 : PrintLine(fpOutput, "</gml:FeatureCollection>");
109 98 : else if (RemoveAppPrefix())
110 2 : PrintLine(fpOutput, "</FeatureCollection>");
111 : else
112 96 : PrintLine(fpOutput, "</%s:FeatureCollection>", pszPrefix);
113 :
114 99 : if (bFpOutputIsNonSeekable)
115 : {
116 0 : VSIFCloseL(fpOutput);
117 0 : fpOutput = nullptr;
118 : }
119 :
120 99 : InsertHeader();
121 :
122 198 : if (!bFpOutputIsNonSeekable && nBoundedByLocation != -1 &&
123 99 : VSIFSeekL(fpOutput, nBoundedByLocation, SEEK_SET) == 0)
124 : {
125 166 : if (m_bWriteGlobalSRS && sBoundingRect.IsInit() &&
126 67 : IsGML3Output())
127 : {
128 57 : bool bCoordSwap = false;
129 : char *pszSRSName =
130 : m_poWriteGlobalSRS
131 57 : ? GML_GetSRSName(m_poWriteGlobalSRS.get(),
132 : eSRSNameFormat, &bCoordSwap)
133 95 : : CPLStrdup("");
134 57 : char szLowerCorner[75] = {};
135 57 : char szUpperCorner[75] = {};
136 :
137 57 : OGRWktOptions coordOpts;
138 :
139 57 : if (OGRGMLDataSource::GetLayerCount() == 1)
140 : {
141 41 : OGRLayer *poLayer = OGRGMLDataSource::GetLayer(0);
142 41 : if (poLayer->GetLayerDefn()->GetGeomFieldCount() == 1)
143 : {
144 : const auto &oCoordPrec =
145 37 : poLayer->GetLayerDefn()
146 37 : ->GetGeomFieldDefn(0)
147 37 : ->GetCoordinatePrecision();
148 37 : if (oCoordPrec.dfXYResolution !=
149 : OGRGeomCoordinatePrecision::UNKNOWN)
150 : {
151 1 : coordOpts.format = OGRWktFormat::F;
152 1 : coordOpts.xyPrecision =
153 : OGRGeomCoordinatePrecision::
154 1 : ResolutionToPrecision(
155 1 : oCoordPrec.dfXYResolution);
156 : }
157 37 : if (oCoordPrec.dfZResolution !=
158 : OGRGeomCoordinatePrecision::UNKNOWN)
159 : {
160 1 : coordOpts.format = OGRWktFormat::F;
161 1 : coordOpts.zPrecision =
162 : OGRGeomCoordinatePrecision::
163 1 : ResolutionToPrecision(
164 1 : oCoordPrec.dfZResolution);
165 : }
166 : }
167 : }
168 :
169 114 : std::string wkt;
170 57 : if (bCoordSwap)
171 : {
172 22 : wkt = OGRMakeWktCoordinate(
173 : sBoundingRect.MinY, sBoundingRect.MinX,
174 22 : sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
175 11 : memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
176 :
177 22 : wkt = OGRMakeWktCoordinate(
178 : sBoundingRect.MaxY, sBoundingRect.MaxX,
179 22 : sBoundingRect.MaxZ, bBBOX3D ? 3 : 2, coordOpts);
180 11 : memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
181 : }
182 : else
183 : {
184 92 : wkt = OGRMakeWktCoordinate(
185 : sBoundingRect.MinX, sBoundingRect.MinY,
186 92 : sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
187 46 : memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
188 :
189 92 : wkt = OGRMakeWktCoordinate(
190 : sBoundingRect.MaxX, sBoundingRect.MaxY,
191 92 : sBoundingRect.MaxZ, (bBBOX3D) ? 3 : 2, coordOpts);
192 46 : memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
193 : }
194 57 : if (bWriteSpaceIndentation)
195 57 : VSIFPrintfL(fpOutput, " ");
196 57 : PrintLine(
197 : fpOutput,
198 : "<gml:boundedBy><gml:Envelope%s%s><gml:lowerCorner>%s"
199 : "</gml:lowerCorner><gml:upperCorner>%s</"
200 : "gml:upperCorner>"
201 : "</gml:Envelope></gml:boundedBy>",
202 57 : bBBOX3D ? " srsDimension=\"3\"" : "", pszSRSName,
203 : szLowerCorner, szUpperCorner);
204 57 : CPLFree(pszSRSName);
205 : }
206 42 : else if (m_bWriteGlobalSRS && sBoundingRect.IsInit())
207 : {
208 10 : if (bWriteSpaceIndentation)
209 10 : VSIFPrintfL(fpOutput, " ");
210 10 : PrintLine(fpOutput, "<gml:boundedBy>");
211 10 : if (bWriteSpaceIndentation)
212 10 : VSIFPrintfL(fpOutput, " ");
213 10 : PrintLine(fpOutput, "<gml:Box>");
214 10 : if (bWriteSpaceIndentation)
215 10 : VSIFPrintfL(fpOutput, " ");
216 10 : VSIFPrintfL(fpOutput,
217 : "<gml:coord><gml:X>%.16g</gml:X>"
218 : "<gml:Y>%.16g</gml:Y>",
219 : sBoundingRect.MinX, sBoundingRect.MinY);
220 10 : if (bBBOX3D)
221 1 : VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
222 : sBoundingRect.MinZ);
223 10 : PrintLine(fpOutput, "</gml:coord>");
224 10 : if (bWriteSpaceIndentation)
225 10 : VSIFPrintfL(fpOutput, " ");
226 10 : VSIFPrintfL(fpOutput,
227 : "<gml:coord><gml:X>%.16g</gml:X>"
228 : "<gml:Y>%.16g</gml:Y>",
229 : sBoundingRect.MaxX, sBoundingRect.MaxY);
230 10 : if (bBBOX3D)
231 1 : VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
232 : sBoundingRect.MaxZ);
233 10 : PrintLine(fpOutput, "</gml:coord>");
234 10 : if (bWriteSpaceIndentation)
235 10 : VSIFPrintfL(fpOutput, " ");
236 10 : PrintLine(fpOutput, "</gml:Box>");
237 10 : if (bWriteSpaceIndentation)
238 10 : VSIFPrintfL(fpOutput, " ");
239 10 : PrintLine(fpOutput, "</gml:boundedBy>");
240 : }
241 : else
242 : {
243 32 : if (bWriteSpaceIndentation)
244 32 : VSIFPrintfL(fpOutput, " ");
245 32 : if (IsGML3Output())
246 32 : PrintLine(
247 : fpOutput,
248 : "<gml:boundedBy><gml:Null /></gml:boundedBy>");
249 : else
250 0 : PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing"
251 : "</gml:null></gml:boundedBy>");
252 : }
253 : }
254 : }
255 :
256 1056 : if (fpOutput)
257 100 : VSIFCloseL(fpOutput);
258 1056 : fpOutput = nullptr;
259 :
260 1056 : CSLDestroy(papszCreateOptions);
261 1056 : papszCreateOptions = nullptr;
262 :
263 1739 : for (int i = 0; i < nLayers; i++)
264 683 : delete papoLayers[i];
265 1056 : CPLFree(papoLayers);
266 1056 : papoLayers = nullptr;
267 1056 : nLayers = 0;
268 :
269 1056 : if (poReader)
270 : {
271 431 : if (bOutIsTempFile)
272 0 : VSIUnlink(poReader->GetSourceFileName());
273 431 : delete poReader;
274 431 : poReader = nullptr;
275 : }
276 :
277 1056 : delete poStoredGMLFeature;
278 1056 : poStoredGMLFeature = nullptr;
279 :
280 1056 : if (m_bUnlinkXSDFilename)
281 : {
282 6 : VSIUnlink(osXSDFilename);
283 6 : m_bUnlinkXSDFilename = false;
284 : }
285 :
286 1056 : if (m_bWriteError)
287 4 : eErr = CE_Failure;
288 : }
289 1056 : return eErr;
290 : }
291 :
292 : /************************************************************************/
293 : /* CheckHeader() */
294 : /************************************************************************/
295 :
296 1539 : bool OGRGMLDataSource::CheckHeader(const char *pszStr)
297 : {
298 1539 : if (strstr(pszStr, "<wfs:FeatureCollection ") != nullptr)
299 374 : return true;
300 :
301 1165 : if (strstr(pszStr, "opengis.net/gml") == nullptr &&
302 364 : strstr(pszStr, "<csw:GetRecordsResponse") == nullptr)
303 : {
304 307 : return false;
305 : }
306 :
307 : // Ignore kml files
308 858 : if (strstr(pszStr, "<kml") != nullptr)
309 : {
310 0 : return false;
311 : }
312 :
313 : // Ignore .xsd schemas.
314 858 : if (strstr(pszStr, "<schema") != nullptr ||
315 857 : strstr(pszStr, "<xs:schema") != nullptr ||
316 857 : strstr(pszStr, "<xsd:schema") != nullptr)
317 : {
318 3 : return false;
319 : }
320 :
321 : // Ignore GeoRSS documents. They will be recognized by the GeoRSS driver.
322 855 : if (strstr(pszStr, "<rss") != nullptr &&
323 4 : strstr(pszStr, "xmlns:georss") != nullptr)
324 : {
325 4 : return false;
326 : }
327 :
328 : // Ignore OpenJUMP .jml documents.
329 : // They will be recognized by the OpenJUMP driver.
330 851 : if (strstr(pszStr, "<JCSDataFile") != nullptr)
331 : {
332 42 : return false;
333 : }
334 :
335 : // Ignore OGR WFS xml description files, or WFS Capabilities results.
336 809 : if (strstr(pszStr, "<OGRWFSDataSource>") != nullptr ||
337 807 : strstr(pszStr, "<wfs:WFS_Capabilities") != nullptr)
338 : {
339 2 : return false;
340 : }
341 :
342 : // Ignore WMTS capabilities results.
343 807 : if (strstr(pszStr, "http://www.opengis.net/wmts/1.0") != nullptr)
344 : {
345 1 : return false;
346 : }
347 :
348 806 : return true;
349 : }
350 :
351 : /************************************************************************/
352 : /* ExtractSRSName() */
353 : /************************************************************************/
354 :
355 27 : static bool ExtractSRSName(const char *pszXML, char *szSRSName,
356 : size_t sizeof_szSRSName)
357 : {
358 27 : szSRSName[0] = '\0';
359 :
360 27 : const char *pszSRSName = strstr(pszXML, "srsName=\"");
361 27 : if (pszSRSName != nullptr)
362 : {
363 23 : pszSRSName += 9;
364 23 : const char *pszEndQuote = strchr(pszSRSName, '"');
365 23 : if (pszEndQuote != nullptr &&
366 23 : static_cast<size_t>(pszEndQuote - pszSRSName) < sizeof_szSRSName)
367 : {
368 23 : memcpy(szSRSName, pszSRSName, pszEndQuote - pszSRSName);
369 23 : szSRSName[pszEndQuote - pszSRSName] = '\0';
370 23 : return true;
371 : }
372 : }
373 4 : return false;
374 : }
375 :
376 : /************************************************************************/
377 : /* Open() */
378 : /************************************************************************/
379 :
380 434 : bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo)
381 :
382 : {
383 : // Extract XSD filename from connection string if present.
384 434 : osFilename = poOpenInfo->pszFilename;
385 434 : const char *pszXSDFilenameTmp = strstr(poOpenInfo->pszFilename, ",xsd=");
386 434 : if (pszXSDFilenameTmp != nullptr)
387 : {
388 2 : osFilename.resize(pszXSDFilenameTmp - poOpenInfo->pszFilename);
389 2 : osXSDFilename = pszXSDFilenameTmp + strlen(",xsd=");
390 : }
391 : else
392 : {
393 : osXSDFilename =
394 432 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "XSD", "");
395 : }
396 :
397 434 : const char *pszFilename = osFilename.c_str();
398 :
399 : // Open the source file.
400 434 : VSILFILE *fpToClose = nullptr;
401 434 : VSILFILE *fp = nullptr;
402 434 : if (poOpenInfo->fpL != nullptr)
403 : {
404 432 : fp = poOpenInfo->fpL;
405 432 : VSIFSeekL(fp, 0, SEEK_SET);
406 : }
407 : else
408 : {
409 2 : fp = VSIFOpenL(pszFilename, "r");
410 2 : if (fp == nullptr)
411 0 : return false;
412 2 : fpToClose = fp;
413 : }
414 :
415 : // Load a header chunk and check for signs it is GML.
416 434 : char szHeader[4096] = {};
417 434 : size_t nRead = VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp);
418 434 : if (nRead == 0)
419 : {
420 0 : if (fpToClose)
421 0 : VSIFCloseL(fpToClose);
422 0 : return false;
423 : }
424 434 : szHeader[nRead] = '\0';
425 :
426 868 : CPLString osWithVsiGzip;
427 :
428 : // Might be a OS-Mastermap gzipped GML, so let be nice and try to open
429 : // it transparently with /vsigzip/.
430 870 : if (static_cast<GByte>(szHeader[0]) == 0x1f &&
431 2 : static_cast<GByte>(szHeader[1]) == 0x8b &&
432 438 : EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "gz") &&
433 2 : !STARTS_WITH(pszFilename, "/vsigzip/"))
434 : {
435 2 : if (fpToClose)
436 0 : VSIFCloseL(fpToClose);
437 2 : fpToClose = nullptr;
438 2 : osWithVsiGzip = "/vsigzip/";
439 2 : osWithVsiGzip += pszFilename;
440 :
441 2 : pszFilename = osWithVsiGzip;
442 :
443 2 : fp = fpToClose = VSIFOpenL(pszFilename, "r");
444 2 : if (fp == nullptr)
445 0 : return false;
446 :
447 2 : nRead = VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp);
448 2 : if (nRead == 0)
449 : {
450 0 : VSIFCloseL(fpToClose);
451 0 : return false;
452 : }
453 2 : szHeader[nRead] = '\0';
454 : }
455 :
456 : // Check for a UTF-8 BOM and skip if found.
457 :
458 : // TODO: BOM is variable-length parameter and depends on encoding. */
459 : // Add BOM detection for other encodings.
460 :
461 : // Used to skip to actual beginning of XML data
462 434 : char *szPtr = szHeader;
463 :
464 434 : if ((static_cast<unsigned char>(szHeader[0]) == 0xEF) &&
465 4 : (static_cast<unsigned char>(szHeader[1]) == 0xBB) &&
466 4 : (static_cast<unsigned char>(szHeader[2]) == 0xBF))
467 : {
468 4 : szPtr += 3;
469 : }
470 :
471 434 : bool bExpatCompatibleEncoding = false;
472 :
473 434 : const char *pszEncoding = strstr(szPtr, "encoding=");
474 434 : if (pszEncoding)
475 348 : bExpatCompatibleEncoding =
476 696 : (pszEncoding[9] == '\'' || pszEncoding[9] == '"') &&
477 348 : (STARTS_WITH_CI(pszEncoding + 10, "UTF-8") ||
478 2 : STARTS_WITH_CI(pszEncoding + 10, "ISO-8859-15") ||
479 2 : (STARTS_WITH_CI(pszEncoding + 10, "ISO-8859-1") &&
480 2 : pszEncoding[20] == pszEncoding[9]));
481 : else
482 86 : bExpatCompatibleEncoding = true; // utf-8 is the default.
483 :
484 843 : const bool bHas3D = strstr(szPtr, "srsDimension=\"3\"") != nullptr ||
485 409 : strstr(szPtr, "<gml:Z>") != nullptr;
486 :
487 : // Here, we expect the opening chevrons of GML tree root element.
488 434 : if (szPtr[0] != '<' || !CheckHeader(szPtr))
489 : {
490 3 : if (fpToClose)
491 0 : VSIFCloseL(fpToClose);
492 3 : return false;
493 : }
494 :
495 : // Now we definitely own the file descriptor.
496 431 : if (fp == poOpenInfo->fpL)
497 427 : poOpenInfo->fpL = nullptr;
498 :
499 : // Small optimization: if we parse a <wfs:FeatureCollection> and
500 : // that numberOfFeatures is set, we can use it to set the FeatureCount
501 : // but *ONLY* if there's just one class.
502 431 : const char *pszFeatureCollection = strstr(szPtr, "wfs:FeatureCollection");
503 431 : if (pszFeatureCollection == nullptr)
504 : // GML 3.2.1 output.
505 286 : pszFeatureCollection = strstr(szPtr, "gml:FeatureCollection");
506 431 : if (pszFeatureCollection == nullptr)
507 : {
508 : // Deegree WFS 1.0.0 output.
509 270 : pszFeatureCollection = strstr(szPtr, "<FeatureCollection");
510 270 : if (pszFeatureCollection &&
511 7 : strstr(szPtr, "xmlns:wfs=\"http://www.opengis.net/wfs\"") ==
512 : nullptr)
513 6 : pszFeatureCollection = nullptr;
514 : }
515 :
516 431 : GIntBig nNumberOfFeatures = 0;
517 431 : if (pszFeatureCollection)
518 : {
519 162 : bExposeGMLId = true;
520 162 : bIsWFS = true;
521 162 : const char *pszNumberOfFeatures = strstr(szPtr, "numberOfFeatures=");
522 162 : if (pszNumberOfFeatures)
523 : {
524 46 : pszNumberOfFeatures += 17;
525 46 : char ch = pszNumberOfFeatures[0];
526 46 : if ((ch == '\'' || ch == '"') &&
527 46 : strchr(pszNumberOfFeatures + 1, ch) != nullptr)
528 : {
529 46 : nNumberOfFeatures = CPLAtoGIntBig(pszNumberOfFeatures + 1);
530 : }
531 : }
532 116 : else if ((pszNumberOfFeatures = strstr(szPtr, "numberReturned=")) !=
533 : nullptr)
534 : {
535 : // WFS 2.0.0
536 93 : pszNumberOfFeatures += 15;
537 93 : char ch = pszNumberOfFeatures[0];
538 93 : if ((ch == '\'' || ch == '"') &&
539 93 : strchr(pszNumberOfFeatures + 1, ch) != nullptr)
540 : {
541 : // 'unknown' might be a valid value in a corrected version of
542 : // WFS 2.0 but it will also evaluate to 0, that is considered as
543 : // unknown, so nothing particular to do.
544 93 : nNumberOfFeatures = CPLAtoGIntBig(pszNumberOfFeatures + 1);
545 : }
546 : }
547 : }
548 269 : else if (STARTS_WITH(pszFilename, "/vsimem/") &&
549 126 : strstr(pszFilename, "_ogr_wfs_"))
550 : {
551 : // http://regis.intergraph.com/wfs/dcmetro/request.asp? returns a
552 : // <G:FeatureCollection> Who knows what servers can return? When
553 : // in the context of the WFS driver always expose the gml:id to avoid
554 : // later crashes.
555 0 : bExposeGMLId = true;
556 0 : bIsWFS = true;
557 : }
558 : else
559 : {
560 374 : bExposeGMLId = strstr(szPtr, " gml:id=\"") != nullptr ||
561 105 : strstr(szPtr, " gml:id='") != nullptr;
562 472 : bExposeFid = strstr(szPtr, " fid=\"") != nullptr ||
563 203 : strstr(szPtr, " fid='") != nullptr;
564 : }
565 :
566 : const char *pszExposeGMLId =
567 431 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "EXPOSE_GML_ID",
568 : CPLGetConfigOption("GML_EXPOSE_GML_ID", nullptr));
569 431 : if (pszExposeGMLId)
570 63 : bExposeGMLId = CPLTestBool(pszExposeGMLId);
571 :
572 : const char *pszExposeFid =
573 431 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "EXPOSE_FID",
574 : CPLGetConfigOption("GML_EXPOSE_FID", nullptr));
575 :
576 431 : if (pszExposeFid)
577 1 : bExposeFid = CPLTestBool(pszExposeFid);
578 :
579 431 : const bool bHintConsiderEPSGAsURN =
580 431 : strstr(szPtr, "xmlns:fme=\"http://www.safe.com/gml/fme\"") != nullptr;
581 :
582 431 : char szSRSName[128] = {};
583 :
584 : // MTKGML.
585 431 : if (strstr(szPtr, "<Maastotiedot") != nullptr)
586 : {
587 3 : if (strstr(szPtr,
588 : "http://xml.nls.fi/XML/Namespace/"
589 : "Maastotietojarjestelma/SiirtotiedostonMalli/2011-02") ==
590 : nullptr)
591 1 : CPLDebug("GML", "Warning: a MTKGML file was detected, "
592 : "but its namespace is unknown");
593 3 : bUseGlobalSRSName = true;
594 3 : if (!ExtractSRSName(szPtr, szSRSName, sizeof(szSRSName)))
595 1 : strcpy(szSRSName, "EPSG:3067");
596 : }
597 :
598 431 : const char *pszSchemaLocation = strstr(szPtr, "schemaLocation=");
599 431 : if (pszSchemaLocation)
600 377 : pszSchemaLocation += strlen("schemaLocation=");
601 :
602 431 : bool bCheckAuxFile = true;
603 431 : if (STARTS_WITH(pszFilename, "/vsicurl_streaming/"))
604 2 : bCheckAuxFile = false;
605 429 : else if (STARTS_WITH(pszFilename, "/vsicurl/") &&
606 1 : (strstr(pszFilename, "?SERVICE=") ||
607 1 : strstr(pszFilename, "&SERVICE=")))
608 0 : bCheckAuxFile = false;
609 :
610 431 : bool bIsWFSJointLayer = bIsWFS && strstr(szPtr, "<wfs:Tuple>");
611 431 : if (bIsWFSJointLayer)
612 50 : bExposeGMLId = false;
613 :
614 : // We assume now that it is GML. Instantiate a GMLReader on it.
615 : const char *pszReadMode =
616 431 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "READ_MODE",
617 : CPLGetConfigOption("GML_READ_MODE", "AUTO"));
618 431 : if (EQUAL(pszReadMode, "AUTO"))
619 428 : pszReadMode = nullptr;
620 431 : if (pszReadMode == nullptr || EQUAL(pszReadMode, "STANDARD"))
621 428 : eReadMode = STANDARD;
622 3 : else if (EQUAL(pszReadMode, "SEQUENTIAL_LAYERS"))
623 2 : eReadMode = SEQUENTIAL_LAYERS;
624 1 : else if (EQUAL(pszReadMode, "INTERLEAVED_LAYERS"))
625 1 : eReadMode = INTERLEAVED_LAYERS;
626 : else
627 : {
628 0 : CPLDebug("GML",
629 : "Unrecognized value for GML_READ_MODE configuration option.");
630 : }
631 :
632 862 : m_bInvertAxisOrderIfLatLong = CPLTestBool(CSLFetchNameValueDef(
633 431 : poOpenInfo->papszOpenOptions, "INVERT_AXIS_ORDER_IF_LAT_LONG",
634 : CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")));
635 :
636 431 : const char *pszConsiderEPSGAsURN = CSLFetchNameValueDef(
637 431 : poOpenInfo->papszOpenOptions, "CONSIDER_EPSG_AS_URN",
638 : CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
639 431 : if (!EQUAL(pszConsiderEPSGAsURN, "AUTO"))
640 0 : m_bConsiderEPSGAsURN = CPLTestBool(pszConsiderEPSGAsURN);
641 431 : else if (bHintConsiderEPSGAsURN)
642 : {
643 : // GML produced by FME (at least CanVec GML) seem to honour EPSG axis
644 : // ordering.
645 5 : CPLDebug("GML", "FME-produced GML --> "
646 : "consider that GML_CONSIDER_EPSG_AS_URN is set to YES");
647 5 : m_bConsiderEPSGAsURN = true;
648 : }
649 : else
650 : {
651 426 : m_bConsiderEPSGAsURN = false;
652 : }
653 :
654 431 : const char *pszSwapCoordinates = CSLFetchNameValueDef(
655 431 : poOpenInfo->papszOpenOptions, "SWAP_COORDINATES",
656 : CPLGetConfigOption("GML_SWAP_COORDINATES", "AUTO"));
657 437 : m_eSwapCoordinates = EQUAL(pszSwapCoordinates, "AUTO") ? GML_SWAP_AUTO
658 6 : : CPLTestBool(pszSwapCoordinates) ? GML_SWAP_YES
659 : : GML_SWAP_NO;
660 :
661 431 : m_bGetSecondaryGeometryOption =
662 431 : CPLTestBool(CPLGetConfigOption("GML_GET_SECONDARY_GEOM", "NO"));
663 :
664 : // EXPAT is faster than Xerces, so when it is safe to use it, use it!
665 : // The only interest of Xerces is for rare encodings that Expat doesn't
666 : // handle, but UTF-8 is well handled by Expat.
667 431 : bool bUseExpatParserPreferably = bExpatCompatibleEncoding;
668 :
669 : // Override default choice.
670 431 : const char *pszGMLParser = CPLGetConfigOption("GML_PARSER", nullptr);
671 431 : if (pszGMLParser)
672 : {
673 4 : if (EQUAL(pszGMLParser, "EXPAT"))
674 2 : bUseExpatParserPreferably = true;
675 2 : else if (EQUAL(pszGMLParser, "XERCES"))
676 2 : bUseExpatParserPreferably = false;
677 : }
678 :
679 431 : poReader =
680 862 : CreateGMLReader(bUseExpatParserPreferably, m_bInvertAxisOrderIfLatLong,
681 431 : m_bConsiderEPSGAsURN, m_eSwapCoordinates,
682 431 : m_bGetSecondaryGeometryOption);
683 431 : if (poReader == nullptr)
684 : {
685 0 : CPLError(CE_Failure, CPLE_AppDefined,
686 : "File %s appears to be GML but the GML reader can't\n"
687 : "be instantiated, likely because Xerces or Expat support was\n"
688 : "not configured in.",
689 : pszFilename);
690 0 : VSIFCloseL(fp);
691 0 : return false;
692 : }
693 :
694 431 : poReader->SetSourceFile(pszFilename);
695 431 : auto poGMLReader = cpl::down_cast<GMLReader *>(poReader);
696 431 : poGMLReader->SetIsWFSJointLayer(bIsWFSJointLayer);
697 431 : bEmptyAsNull =
698 431 : CPLFetchBool(poOpenInfo->papszOpenOptions, "EMPTY_AS_NULL", true);
699 431 : poGMLReader->SetEmptyAsNull(bEmptyAsNull);
700 431 : poGMLReader->SetReportAllAttributes(CPLFetchBool(
701 431 : poOpenInfo->papszOpenOptions, "GML_ATTRIBUTES_TO_OGR_FIELDS",
702 431 : CPLTestBool(CPLGetConfigOption("GML_ATTRIBUTES_TO_OGR_FIELDS", "NO"))));
703 431 : poGMLReader->SetUseBBOX(
704 431 : CPLFetchBool(poOpenInfo->papszOpenOptions, "USE_BBOX", false));
705 :
706 : // Find <gml:description>, <gml:name> and <gml:boundedBy> and if it is
707 : // a standalone geometry
708 : // Also look for <gml:description>, <gml:identifier> and <gml:name> inside
709 : // a feature
710 431 : FindAndParseTopElements(fp);
711 :
712 431 : if (m_poStandaloneGeom)
713 : {
714 1 : papoLayers = static_cast<OGRLayer **>(CPLMalloc(sizeof(OGRLayer *)));
715 1 : nLayers = 1;
716 : auto poLayer = new OGRMemLayer(
717 : "geometry",
718 1 : m_oStandaloneGeomSRS.IsEmpty() ? nullptr : &m_oStandaloneGeomSRS,
719 1 : m_poStandaloneGeom->getGeometryType());
720 1 : papoLayers[0] = poLayer;
721 1 : OGRFeature *poFeature = new OGRFeature(poLayer->GetLayerDefn());
722 1 : poFeature->SetGeometryDirectly(m_poStandaloneGeom.release());
723 1 : CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
724 1 : delete poFeature;
725 1 : poLayer->SetUpdatable(false);
726 1 : VSIFCloseL(fp);
727 1 : return true;
728 : }
729 :
730 430 : if (szSRSName[0] != '\0')
731 3 : poReader->SetGlobalSRSName(szSRSName);
732 :
733 : const bool bIsWFSFromServer =
734 430 : CPLString(pszFilename).ifind("SERVICE=WFS") != std::string::npos;
735 :
736 : // Resolve the xlinks in the source file and save it with the
737 : // extension ".resolved.gml". The source file will to set to that.
738 430 : char *pszXlinkResolvedFilename = nullptr;
739 430 : const char *pszOption = CPLGetConfigOption("GML_SAVE_RESOLVED_TO", nullptr);
740 430 : bool bResolve = true;
741 430 : bool bHugeFile = false;
742 430 : if (bIsWFSFromServer ||
743 0 : (pszOption != nullptr && STARTS_WITH_CI(pszOption, "SAME")))
744 : {
745 : // "SAME" will overwrite the existing gml file.
746 59 : pszXlinkResolvedFilename = CPLStrdup(pszFilename);
747 : }
748 371 : else if (pszOption != nullptr && CPLStrnlen(pszOption, 5) >= 5 &&
749 0 : STARTS_WITH_CI(pszOption - 4 + strlen(pszOption), ".gml"))
750 : {
751 : // Any string ending with ".gml" will try and write to it.
752 0 : pszXlinkResolvedFilename = CPLStrdup(pszOption);
753 : }
754 : else
755 : {
756 : // When no option is given or is not recognised,
757 : // use the same file name with the extension changed to .resolved.gml
758 371 : pszXlinkResolvedFilename = CPLStrdup(
759 742 : CPLResetExtensionSafe(pszFilename, "resolved.gml").c_str());
760 :
761 : // Check if the file already exists.
762 : VSIStatBufL sResStatBuf, sGMLStatBuf;
763 742 : if (bCheckAuxFile &&
764 371 : VSIStatL(pszXlinkResolvedFilename, &sResStatBuf) == 0)
765 : {
766 6 : if (VSIStatL(pszFilename, &sGMLStatBuf) == 0 &&
767 3 : sGMLStatBuf.st_mtime > sResStatBuf.st_mtime)
768 : {
769 0 : CPLDebug("GML",
770 : "Found %s but ignoring because it appears\n"
771 : "be older than the associated GML file.",
772 : pszXlinkResolvedFilename);
773 : }
774 : else
775 : {
776 3 : poReader->SetSourceFile(pszXlinkResolvedFilename);
777 3 : bResolve = false;
778 : }
779 : }
780 : }
781 :
782 : const char *pszSkipOption =
783 430 : CPLGetConfigOption("GML_SKIP_RESOLVE_ELEMS", "ALL");
784 430 : char **papszSkip = nullptr;
785 430 : if (EQUAL(pszSkipOption, "ALL"))
786 424 : bResolve = false;
787 6 : else if (EQUAL(pszSkipOption, "HUGE"))
788 : // Exactly as NONE, but intended for HUGE files
789 3 : bHugeFile = true;
790 3 : else if (!EQUAL(pszSkipOption, "NONE")) // Use this to resolve everything.
791 0 : papszSkip = CSLTokenizeString2(
792 : pszSkipOption, ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
793 430 : bool bHaveSchema = false;
794 430 : bool bSchemaDone = false;
795 :
796 : // Is some GML Feature Schema (.gfs) TEMPLATE required?
797 : const char *pszGFSTemplateName =
798 430 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "GFS_TEMPLATE",
799 : CPLGetConfigOption("GML_GFS_TEMPLATE", nullptr));
800 430 : if (pszGFSTemplateName != nullptr)
801 : {
802 : // Attempt to load the GFS TEMPLATE.
803 2 : bHaveSchema = poReader->LoadClasses(pszGFSTemplateName);
804 : }
805 :
806 430 : if (bResolve)
807 : {
808 6 : if (bHugeFile)
809 : {
810 3 : bSchemaDone = true;
811 : bool bSqliteIsTempFile =
812 3 : CPLTestBool(CPLGetConfigOption("GML_HUGE_TEMPFILE", "YES"));
813 : int iSqliteCacheMB =
814 3 : atoi(CPLGetConfigOption("OGR_SQLITE_CACHE", "0"));
815 6 : if (poReader->HugeFileResolver(pszXlinkResolvedFilename,
816 : bSqliteIsTempFile,
817 3 : iSqliteCacheMB) == false)
818 : {
819 : // Assume an error has been reported.
820 0 : VSIFCloseL(fp);
821 0 : CPLFree(pszXlinkResolvedFilename);
822 0 : return false;
823 : }
824 : }
825 : else
826 : {
827 3 : poReader->ResolveXlinks(pszXlinkResolvedFilename, &bOutIsTempFile,
828 3 : papszSkip);
829 : }
830 : }
831 :
832 430 : CPLFree(pszXlinkResolvedFilename);
833 430 : pszXlinkResolvedFilename = nullptr;
834 430 : CSLDestroy(papszSkip);
835 430 : papszSkip = nullptr;
836 :
837 : // If the source filename for the reader is still the GML filename, then
838 : // we can directly provide the file pointer. Otherwise close it.
839 430 : if (strcmp(poReader->GetSourceFileName(), pszFilename) == 0)
840 421 : poReader->SetFP(fp);
841 : else
842 9 : VSIFCloseL(fp);
843 430 : fp = nullptr;
844 :
845 : // Is a prescan required?
846 430 : if (bHaveSchema && !bSchemaDone)
847 : {
848 : // We must detect which layers are actually present in the .gml
849 : // and how many features they have.
850 2 : if (!poReader->PrescanForTemplate())
851 : {
852 : // Assume an error has been reported.
853 0 : return false;
854 : }
855 : }
856 :
857 860 : CPLString osGFSFilename;
858 430 : if (!bIsWFSFromServer)
859 : {
860 371 : osGFSFilename = CPLResetExtensionSafe(pszFilename, "gfs");
861 371 : if (STARTS_WITH(osGFSFilename, "/vsigzip/"))
862 2 : osGFSFilename = osGFSFilename.substr(strlen("/vsigzip/"));
863 : }
864 :
865 : // Can we find a GML Feature Schema (.gfs) for the input file?
866 796 : if (!osGFSFilename.empty() && !bHaveSchema && !bSchemaDone &&
867 366 : osXSDFilename.empty())
868 : {
869 : VSIStatBufL sGFSStatBuf;
870 363 : if (bCheckAuxFile && VSIStatL(osGFSFilename, &sGFSStatBuf) == 0)
871 : {
872 : VSIStatBufL sGMLStatBuf;
873 92 : if (VSIStatL(pszFilename, &sGMLStatBuf) == 0 &&
874 46 : sGMLStatBuf.st_mtime > sGFSStatBuf.st_mtime)
875 : {
876 1 : CPLDebug("GML",
877 : "Found %s but ignoring because it appears\n"
878 : "be older than the associated GML file.",
879 : osGFSFilename.c_str());
880 : }
881 : else
882 : {
883 45 : bHaveSchema = poReader->LoadClasses(osGFSFilename);
884 45 : if (bHaveSchema)
885 : {
886 : const std::string osXSDFilenameTmp =
887 86 : CPLResetExtensionSafe(pszFilename, "xsd");
888 43 : if (VSIStatExL(osXSDFilenameTmp.c_str(), &sGMLStatBuf,
889 43 : VSI_STAT_EXISTS_FLAG) == 0)
890 : {
891 0 : CPLDebug("GML", "Using %s file, ignoring %s",
892 : osGFSFilename.c_str(),
893 : osXSDFilenameTmp.c_str());
894 : }
895 : }
896 : }
897 : }
898 : }
899 :
900 : // Can we find an xsd which might conform to the GML3 Level 0
901 : // profile? We really ought to look for it based on the rules
902 : // schemaLocation in the GML feature collection but for now we
903 : // just hopes it is in the same director with the same name.
904 :
905 430 : bool bHasFoundXSD = false;
906 :
907 430 : if (!bHaveSchema)
908 : {
909 385 : char **papszTypeNames = nullptr;
910 :
911 : VSIStatBufL sXSDStatBuf;
912 385 : if (osXSDFilename.empty())
913 : {
914 323 : osXSDFilename = CPLResetExtensionSafe(pszFilename, "xsd");
915 323 : if (bCheckAuxFile && VSIStatExL(osXSDFilename, &sXSDStatBuf,
916 : VSI_STAT_EXISTS_FLAG) == 0)
917 : {
918 213 : bHasFoundXSD = true;
919 : }
920 : }
921 : else
922 : {
923 62 : if (STARTS_WITH(osXSDFilename, "http://") ||
924 124 : STARTS_WITH(osXSDFilename, "https://") ||
925 62 : VSIStatExL(osXSDFilename, &sXSDStatBuf, VSI_STAT_EXISTS_FLAG) ==
926 : 0)
927 : {
928 62 : bHasFoundXSD = true;
929 : }
930 : }
931 :
932 : // If not found, try if there is a schema in the gml_registry.xml
933 : // that might match a declared namespace and featuretype.
934 385 : if (!bHasFoundXSD)
935 : {
936 : GMLRegistry oRegistry(
937 110 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "REGISTRY",
938 220 : CPLGetConfigOption("GML_REGISTRY", "")));
939 110 : if (oRegistry.Parse())
940 : {
941 220 : CPLString osHeader(szHeader);
942 735 : for (size_t iNS = 0; iNS < oRegistry.aoNamespaces.size(); iNS++)
943 : {
944 : GMLRegistryNamespace &oNamespace =
945 635 : oRegistry.aoNamespaces[iNS];
946 : // When namespace is omitted or fit with case sensitive
947 : // match for name space prefix, then go next to find feature
948 : // match.
949 : //
950 : // Case sensitive comparison since below test that also
951 : // uses the namespace prefix is case sensitive.
952 1167 : if (!oNamespace.osPrefix.empty() &&
953 532 : osHeader.find(CPLSPrintf(
954 : "xmlns:%s", oNamespace.osPrefix.c_str())) ==
955 : std::string::npos)
956 : {
957 : // namespace does not match with one of registry
958 : // definition. go to next entry.
959 525 : continue;
960 : }
961 :
962 : const char *pszURIToFind =
963 110 : CPLSPrintf("\"%s\"", oNamespace.osURI.c_str());
964 110 : if (strstr(szHeader, pszURIToFind) != nullptr)
965 : {
966 10 : if (oNamespace.bUseGlobalSRSName)
967 7 : bUseGlobalSRSName = true;
968 :
969 38 : for (size_t iTypename = 0;
970 38 : iTypename < oNamespace.aoFeatureTypes.size();
971 : iTypename++)
972 : {
973 38 : const char *pszElementToFind = nullptr;
974 :
975 : GMLRegistryFeatureType &oFeatureType =
976 38 : oNamespace.aoFeatureTypes[iTypename];
977 :
978 38 : if (!oNamespace.osPrefix.empty())
979 : {
980 15 : if (!oFeatureType.osElementValue.empty())
981 4 : pszElementToFind = CPLSPrintf(
982 : "%s:%s>%s", oNamespace.osPrefix.c_str(),
983 : oFeatureType.osElementName.c_str(),
984 : oFeatureType.osElementValue.c_str());
985 : else
986 11 : pszElementToFind = CPLSPrintf(
987 : "%s:%s", oNamespace.osPrefix.c_str(),
988 : oFeatureType.osElementName.c_str());
989 : }
990 : else
991 : {
992 23 : if (!oFeatureType.osElementValue.empty())
993 0 : pszElementToFind = CPLSPrintf(
994 : "%s>%s",
995 : oFeatureType.osElementName.c_str(),
996 : oFeatureType.osElementValue.c_str());
997 : else
998 23 : pszElementToFind = CPLSPrintf(
999 : "<%s",
1000 : oFeatureType.osElementName.c_str());
1001 : }
1002 :
1003 : // Case sensitive test since in a CadastralParcel
1004 : // feature there is a property basicPropertyUnit
1005 : // xlink, not to be confused with a top-level
1006 : // BasicPropertyUnit feature.
1007 38 : if (osHeader.find(pszElementToFind) !=
1008 : std::string::npos)
1009 : {
1010 10 : if (!oFeatureType.osSchemaLocation.empty())
1011 : {
1012 : osXSDFilename =
1013 1 : oFeatureType.osSchemaLocation;
1014 1 : if (STARTS_WITH(osXSDFilename, "http://") ||
1015 1 : STARTS_WITH(osXSDFilename,
1016 2 : "https://") ||
1017 1 : VSIStatExL(osXSDFilename, &sXSDStatBuf,
1018 : VSI_STAT_EXISTS_FLAG) == 0)
1019 : {
1020 1 : bHasFoundXSD = true;
1021 1 : bHaveSchema = true;
1022 1 : CPLDebug(
1023 : "GML",
1024 : "Found %s for %s:%s in registry",
1025 : osXSDFilename.c_str(),
1026 : oNamespace.osPrefix.c_str(),
1027 : oFeatureType.osElementName.c_str());
1028 : }
1029 : else
1030 : {
1031 0 : CPLDebug("GML", "Cannot open %s",
1032 : osXSDFilename.c_str());
1033 : }
1034 : }
1035 : else
1036 : {
1037 9 : bHaveSchema = poReader->LoadClasses(
1038 9 : oFeatureType.osGFSSchemaLocation);
1039 9 : if (bHaveSchema)
1040 : {
1041 9 : CPLDebug(
1042 : "GML",
1043 : "Found %s for %s:%s in registry",
1044 : oFeatureType.osGFSSchemaLocation
1045 : .c_str(),
1046 : oNamespace.osPrefix.c_str(),
1047 : oFeatureType.osElementName.c_str());
1048 : }
1049 : }
1050 10 : break;
1051 : }
1052 : }
1053 10 : break;
1054 : }
1055 : }
1056 : }
1057 : }
1058 :
1059 : /* For WFS, try to fetch the application schema */
1060 385 : if (bIsWFS && !bHaveSchema && pszSchemaLocation != nullptr &&
1061 141 : (pszSchemaLocation[0] == '\'' || pszSchemaLocation[0] == '"') &&
1062 141 : strchr(pszSchemaLocation + 1, pszSchemaLocation[0]) != nullptr)
1063 : {
1064 141 : char *pszSchemaLocationTmp1 = CPLStrdup(pszSchemaLocation + 1);
1065 141 : int nTruncLen = static_cast<int>(
1066 141 : strchr(pszSchemaLocation + 1, pszSchemaLocation[0]) -
1067 141 : (pszSchemaLocation + 1));
1068 141 : pszSchemaLocationTmp1[nTruncLen] = '\0';
1069 : char *pszSchemaLocationTmp2 =
1070 141 : CPLUnescapeString(pszSchemaLocationTmp1, nullptr, CPLES_XML);
1071 : CPLString osEscaped =
1072 282 : ReplaceSpaceByPct20IfNeeded(pszSchemaLocationTmp2);
1073 141 : CPLFree(pszSchemaLocationTmp2);
1074 141 : pszSchemaLocationTmp2 = CPLStrdup(osEscaped);
1075 141 : if (pszSchemaLocationTmp2)
1076 : {
1077 : // pszSchemaLocationTmp2 is of the form:
1078 : // http://namespace1 http://namespace1_schema_location
1079 : // http://namespace2 http://namespace1_schema_location2 So we
1080 : // try to find http://namespace1_schema_location that contains
1081 : // hints that it is the WFS application */ schema, i.e. if it
1082 : // contains typename= and request=DescribeFeatureType.
1083 : char **papszTokens =
1084 141 : CSLTokenizeString2(pszSchemaLocationTmp2, " \r\n", 0);
1085 141 : int nTokens = CSLCount(papszTokens);
1086 141 : if ((nTokens % 2) == 0)
1087 : {
1088 299 : for (int i = 0; i < nTokens; i += 2)
1089 : {
1090 283 : const char *pszEscapedURL = papszTokens[i + 1];
1091 283 : char *pszLocation = CPLUnescapeString(
1092 : pszEscapedURL, nullptr, CPLES_URL);
1093 283 : CPLString osLocation = pszLocation;
1094 283 : CPLFree(pszLocation);
1095 283 : if (osLocation.ifind("typename=") !=
1096 408 : std::string::npos &&
1097 125 : osLocation.ifind("request=DescribeFeatureType") !=
1098 : std::string::npos)
1099 : {
1100 : CPLString osTypeName =
1101 250 : CPLURLGetValue(osLocation, "typename");
1102 : papszTypeNames =
1103 125 : CSLTokenizeString2(osTypeName, ",", 0);
1104 :
1105 : // Old non-documented way
1106 : const char *pszGML_DOWNLOAD_WFS_SCHEMA =
1107 125 : CPLGetConfigOption("GML_DOWNLOAD_WFS_SCHEMA",
1108 : nullptr);
1109 125 : if (pszGML_DOWNLOAD_WFS_SCHEMA)
1110 : {
1111 0 : CPLError(
1112 : CE_Warning, CPLE_AppDefined,
1113 : "Configuration option "
1114 : "GML_DOWNLOAD_WFS_SCHEMA is deprecated. "
1115 : "Please use GML_DOWNLOAD_SCHEMA instead of "
1116 : "the DOWNLOAD_SCHEMA open option");
1117 : }
1118 : else
1119 : {
1120 125 : pszGML_DOWNLOAD_WFS_SCHEMA = "YES";
1121 : }
1122 132 : if (!bHasFoundXSD && CPLHTTPEnabled() &&
1123 7 : CPLFetchBool(poOpenInfo->papszOpenOptions,
1124 : "DOWNLOAD_SCHEMA",
1125 7 : CPLTestBool(CPLGetConfigOption(
1126 : "GML_DOWNLOAD_SCHEMA",
1127 : pszGML_DOWNLOAD_WFS_SCHEMA))))
1128 : {
1129 : CPLHTTPResult *psResult =
1130 7 : CPLHTTPFetch(pszEscapedURL, nullptr);
1131 7 : if (psResult)
1132 : {
1133 7 : if (psResult->nStatus == 0 &&
1134 6 : psResult->pabyData != nullptr)
1135 : {
1136 6 : bHasFoundXSD = true;
1137 6 : m_bUnlinkXSDFilename = true;
1138 : osXSDFilename =
1139 : VSIMemGenerateHiddenFilename(
1140 6 : "tmp_ogr_gml.xsd");
1141 6 : VSILFILE *fpMem = VSIFileFromMemBuffer(
1142 : osXSDFilename, psResult->pabyData,
1143 6 : psResult->nDataLen, TRUE);
1144 6 : VSIFCloseL(fpMem);
1145 6 : psResult->pabyData = nullptr;
1146 : }
1147 7 : CPLHTTPDestroyResult(psResult);
1148 : }
1149 : }
1150 125 : break;
1151 : }
1152 : }
1153 : }
1154 141 : CSLDestroy(papszTokens);
1155 : }
1156 141 : CPLFree(pszSchemaLocationTmp2);
1157 141 : CPLFree(pszSchemaLocationTmp1);
1158 : }
1159 :
1160 385 : bool bHasFeatureProperties = false;
1161 385 : if (bHasFoundXSD)
1162 : {
1163 564 : std::vector<GMLFeatureClass *> aosClasses;
1164 564 : bool bUseSchemaImports = CPLFetchBool(
1165 282 : poOpenInfo->papszOpenOptions, "USE_SCHEMA_IMPORT",
1166 282 : CPLTestBool(CPLGetConfigOption("GML_USE_SCHEMA_IMPORT", "NO")));
1167 282 : bool bFullyUnderstood = false;
1168 282 : bHaveSchema = GMLParseXSD(osXSDFilename, bUseSchemaImports,
1169 : aosClasses, bFullyUnderstood);
1170 :
1171 282 : if (bHaveSchema && !bFullyUnderstood && bIsWFSJointLayer)
1172 : {
1173 1 : CPLDebug("GML", "Schema found, but only partially understood. "
1174 : "Cannot be used in a WFS join context");
1175 :
1176 : std::vector<GMLFeatureClass *>::const_iterator oIter =
1177 1 : aosClasses.begin();
1178 : std::vector<GMLFeatureClass *>::const_iterator oEndIter =
1179 1 : aosClasses.end();
1180 2 : while (oIter != oEndIter)
1181 : {
1182 1 : GMLFeatureClass *poClass = *oIter;
1183 :
1184 1 : delete poClass;
1185 1 : ++oIter;
1186 : }
1187 1 : aosClasses.resize(0);
1188 1 : bHaveSchema = false;
1189 : }
1190 :
1191 282 : if (bHaveSchema)
1192 : {
1193 273 : CPLDebug("GML", "Using %s", osXSDFilename.c_str());
1194 : std::vector<GMLFeatureClass *>::const_iterator oIter =
1195 273 : aosClasses.begin();
1196 : std::vector<GMLFeatureClass *>::const_iterator oEndIter =
1197 273 : aosClasses.end();
1198 658 : while (oIter != oEndIter)
1199 : {
1200 386 : GMLFeatureClass *poClass = *oIter;
1201 :
1202 386 : if (poClass->HasFeatureProperties())
1203 : {
1204 1 : bHasFeatureProperties = true;
1205 1 : break;
1206 : }
1207 385 : ++oIter;
1208 : }
1209 :
1210 273 : oIter = aosClasses.begin();
1211 661 : while (oIter != oEndIter)
1212 : {
1213 388 : GMLFeatureClass *poClass = *oIter;
1214 388 : ++oIter;
1215 :
1216 : // We have no way of knowing if the geometry type is 25D
1217 : // when examining the xsd only, so if there was a hint
1218 : // it is, we force to 25D.
1219 388 : if (bHas3D && poClass->GetGeometryPropertyCount() == 1)
1220 : {
1221 35 : poClass->GetGeometryProperty(0)->SetType(
1222 : wkbSetZ(static_cast<OGRwkbGeometryType>(
1223 : poClass->GetGeometryProperty(0)->GetType())));
1224 : }
1225 :
1226 388 : bool bAddClass = true;
1227 : // If typenames are declared, only register the matching
1228 : // classes, in case the XSD contains more layers, but not if
1229 : // feature classes contain feature properties, in which case
1230 : // we will have embedded features that will be reported as
1231 : // top-level features.
1232 388 : if (papszTypeNames != nullptr && !bHasFeatureProperties)
1233 : {
1234 178 : bAddClass = false;
1235 178 : char **papszIter = papszTypeNames;
1236 416 : while (*papszIter && !bAddClass)
1237 : {
1238 238 : const char *pszTypeName = *papszIter;
1239 238 : if (strcmp(pszTypeName, poClass->GetName()) == 0)
1240 177 : bAddClass = true;
1241 238 : papszIter++;
1242 : }
1243 :
1244 : // Retry by removing prefixes.
1245 178 : if (!bAddClass)
1246 : {
1247 1 : papszIter = papszTypeNames;
1248 2 : while (*papszIter && !bAddClass)
1249 : {
1250 1 : const char *pszTypeName = *papszIter;
1251 1 : const char *pszColon = strchr(pszTypeName, ':');
1252 1 : if (pszColon)
1253 : {
1254 1 : pszTypeName = pszColon + 1;
1255 1 : if (strcmp(pszTypeName,
1256 1 : poClass->GetName()) == 0)
1257 : {
1258 1 : poClass->SetName(pszTypeName);
1259 1 : bAddClass = true;
1260 : }
1261 : }
1262 1 : papszIter++;
1263 : }
1264 : }
1265 : }
1266 :
1267 776 : if (bAddClass &&
1268 388 : poReader->GetClass(poClass->GetName()) == nullptr)
1269 : {
1270 388 : poReader->AddClass(poClass);
1271 : }
1272 : else
1273 0 : delete poClass;
1274 : }
1275 :
1276 273 : poReader->SetClassListLocked(true);
1277 : }
1278 : }
1279 :
1280 385 : if (bHaveSchema && bIsWFS)
1281 : {
1282 136 : if (bIsWFSJointLayer)
1283 : {
1284 47 : BuildJointClassFromXSD();
1285 : }
1286 :
1287 : // For WFS, we can assume sequential layers.
1288 155 : if (poReader->GetClassCount() > 1 && pszReadMode == nullptr &&
1289 19 : !bHasFeatureProperties)
1290 : {
1291 19 : CPLDebug("GML",
1292 : "WFS output. Using SEQUENTIAL_LAYERS read mode");
1293 19 : eReadMode = SEQUENTIAL_LAYERS;
1294 : }
1295 : // Sometimes the returned schema contains only <xs:include> that we
1296 : // don't resolve so ignore it.
1297 117 : else if (poReader->GetClassCount() == 0)
1298 0 : bHaveSchema = false;
1299 : }
1300 :
1301 385 : CSLDestroy(papszTypeNames);
1302 : }
1303 :
1304 : // Force a first pass to establish the schema. Eventually we will have
1305 : // mechanisms for remembering the schema and related information.
1306 : const char *pszForceSRSDetection =
1307 430 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "FORCE_SRS_DETECTION");
1308 439 : if (!bHaveSchema ||
1309 9 : (pszForceSRSDetection && CPLTestBool(pszForceSRSDetection)))
1310 : {
1311 112 : bool bOnlyDetectSRS = bHaveSchema;
1312 112 : if (!poReader->PrescanForSchema(true, bOnlyDetectSRS))
1313 : {
1314 : // Assume an error was reported.
1315 0 : return false;
1316 : }
1317 112 : if (!bHaveSchema)
1318 : {
1319 103 : if (bIsWFSJointLayer && poReader->GetClassCount() == 1)
1320 : {
1321 2 : BuildJointClassFromScannedSchema();
1322 : }
1323 :
1324 103 : if (bHasFoundXSD)
1325 : {
1326 9 : CPLDebug("GML", "Generating %s file, ignoring %s",
1327 : osGFSFilename.c_str(), osXSDFilename.c_str());
1328 : }
1329 : }
1330 : }
1331 :
1332 430 : if (poReader->GetClassCount() > 1 && poReader->IsSequentialLayers() &&
1333 : pszReadMode == nullptr)
1334 : {
1335 21 : CPLDebug("GML",
1336 : "Layers are monoblock. Using SEQUENTIAL_LAYERS read mode");
1337 21 : eReadMode = SEQUENTIAL_LAYERS;
1338 : }
1339 :
1340 430 : if (!DealWithOgrSchemaOpenOption(poOpenInfo))
1341 : {
1342 10 : return false;
1343 : }
1344 :
1345 : // Save the schema file if possible. Don't make a fuss if we
1346 : // can't. It could be read-only directory or something.
1347 : const char *pszWriteGFS =
1348 420 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "WRITE_GFS", "AUTO");
1349 420 : bool bWriteGFS = false;
1350 420 : if (EQUAL(pszWriteGFS, "AUTO"))
1351 : {
1352 101 : if (!bHaveSchema && !poReader->HasStoppedParsing() &&
1353 615 : VSIIsLocal(pszFilename) &&
1354 97 : VSISupportsSequentialWrite(pszFilename, false))
1355 : {
1356 : VSIStatBufL sGFSStatBuf;
1357 97 : if (VSIStatExL(osGFSFilename, &sGFSStatBuf, VSI_STAT_EXISTS_FLAG) !=
1358 : 0)
1359 : {
1360 93 : bWriteGFS = true;
1361 : }
1362 : else
1363 : {
1364 4 : CPLDebug("GML", "Not saving %s file: already exists.",
1365 : osGFSFilename.c_str());
1366 : }
1367 : }
1368 : }
1369 3 : else if (CPLTestBool(pszWriteGFS))
1370 : {
1371 1 : if (bHaveSchema || !poReader->HasStoppedParsing())
1372 : {
1373 1 : bWriteGFS = true;
1374 : }
1375 : else
1376 : {
1377 0 : CPLError(CE_Warning, CPLE_AppDefined,
1378 : "GFS file saving asked, but cannot be done");
1379 : }
1380 : }
1381 :
1382 420 : if (bWriteGFS)
1383 : {
1384 94 : if (!poReader->SaveClasses(osGFSFilename))
1385 : {
1386 0 : if (CPLTestBool(pszWriteGFS))
1387 : {
1388 0 : CPLError(CE_Warning, CPLE_AppDefined,
1389 : "GFS file saving asked, but failed");
1390 : }
1391 : else
1392 : {
1393 0 : CPLDebug("GML", "Not saving %s file: can't be created.",
1394 : osGFSFilename.c_str());
1395 : }
1396 : }
1397 : }
1398 :
1399 : // Translate the GMLFeatureClasses into layers.
1400 420 : papoLayers = static_cast<OGRLayer **>(
1401 420 : CPLCalloc(sizeof(OGRLayer *), poReader->GetClassCount()));
1402 420 : nLayers = 0;
1403 :
1404 420 : if (poReader->GetClassCount() == 1 && nNumberOfFeatures != 0)
1405 : {
1406 117 : GMLFeatureClass *poClass = poReader->GetClass(0);
1407 117 : GIntBig nFeatureCount = poClass->GetFeatureCount();
1408 117 : if (nFeatureCount < 0)
1409 : {
1410 106 : poClass->SetFeatureCount(nNumberOfFeatures);
1411 : }
1412 11 : else if (nFeatureCount != nNumberOfFeatures)
1413 : {
1414 0 : CPLDebug("GML", "Feature count in header, "
1415 : "and actual feature count don't match");
1416 : }
1417 : }
1418 :
1419 420 : if (bIsWFS && poReader->GetClassCount() == 1)
1420 137 : bUseGlobalSRSName = true;
1421 :
1422 976 : while (nLayers < poReader->GetClassCount())
1423 : {
1424 556 : papoLayers[nLayers] = TranslateGMLSchema(poReader->GetClass(nLayers));
1425 556 : nLayers++;
1426 : }
1427 :
1428 : // Warn if we have geometry columns without known CRS due to only using
1429 : // the .xsd
1430 420 : if (bHaveSchema && pszForceSRSDetection == nullptr)
1431 : {
1432 308 : bool bExitLoop = false;
1433 659 : for (int i = 0; !bExitLoop && i < nLayers; ++i)
1434 : {
1435 351 : const auto poLayer = papoLayers[i];
1436 351 : const auto poLayerDefn = poLayer->GetLayerDefn();
1437 351 : const auto nGeomFieldCount = poLayerDefn->GetGeomFieldCount();
1438 481 : for (int j = 0; j < nGeomFieldCount; ++j)
1439 : {
1440 366 : if (poLayerDefn->GetGeomFieldDefn(j)->GetSpatialRef() ==
1441 : nullptr)
1442 : {
1443 236 : bExitLoop = true;
1444 236 : break;
1445 : }
1446 : }
1447 : }
1448 308 : if (bExitLoop)
1449 : {
1450 236 : CPLDebug("GML",
1451 : "Geometry fields without known CRS have been detected. "
1452 : "You may want to specify the FORCE_SRS_DETECTION open "
1453 : "option to YES.");
1454 : }
1455 : }
1456 :
1457 420 : return true;
1458 : }
1459 :
1460 : /************************************************************************/
1461 : /* BuildJointClassFromXSD() */
1462 : /************************************************************************/
1463 :
1464 47 : void OGRGMLDataSource::BuildJointClassFromXSD()
1465 : {
1466 94 : CPLString osJointClassName = "join";
1467 141 : for (int i = 0; i < poReader->GetClassCount(); i++)
1468 : {
1469 94 : osJointClassName += "_";
1470 94 : osJointClassName += poReader->GetClass(i)->GetName();
1471 : }
1472 47 : GMLFeatureClass *poJointClass = new GMLFeatureClass(osJointClassName);
1473 47 : poJointClass->SetElementName("Tuple");
1474 141 : for (int i = 0; i < poReader->GetClassCount(); i++)
1475 : {
1476 94 : GMLFeatureClass *poClass = poReader->GetClass(i);
1477 :
1478 : {
1479 188 : CPLString osPropertyName;
1480 94 : osPropertyName.Printf("%s.%s", poClass->GetName(), "gml_id");
1481 : GMLPropertyDefn *poNewProperty =
1482 94 : new GMLPropertyDefn(osPropertyName);
1483 188 : CPLString osSrcElement;
1484 94 : osSrcElement.Printf("member|%s@id", poClass->GetName());
1485 94 : poNewProperty->SetSrcElement(osSrcElement);
1486 94 : poNewProperty->SetType(GMLPT_String);
1487 94 : poJointClass->AddProperty(poNewProperty);
1488 : }
1489 :
1490 200 : for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
1491 : {
1492 106 : GMLPropertyDefn *poProperty = poClass->GetProperty(iField);
1493 212 : CPLString osPropertyName;
1494 : osPropertyName.Printf("%s.%s", poClass->GetName(),
1495 106 : poProperty->GetName());
1496 : GMLPropertyDefn *poNewProperty =
1497 106 : new GMLPropertyDefn(osPropertyName);
1498 :
1499 106 : poNewProperty->SetType(poProperty->GetType());
1500 212 : CPLString osSrcElement;
1501 : osSrcElement.Printf("member|%s|%s", poClass->GetName(),
1502 106 : poProperty->GetSrcElement());
1503 106 : poNewProperty->SetSrcElement(osSrcElement);
1504 106 : poNewProperty->SetWidth(poProperty->GetWidth());
1505 106 : poNewProperty->SetPrecision(poProperty->GetPrecision());
1506 106 : poNewProperty->SetNullable(poProperty->IsNullable());
1507 :
1508 106 : poJointClass->AddProperty(poNewProperty);
1509 : }
1510 188 : for (int iField = 0; iField < poClass->GetGeometryPropertyCount();
1511 : iField++)
1512 : {
1513 : GMLGeometryPropertyDefn *poProperty =
1514 94 : poClass->GetGeometryProperty(iField);
1515 188 : CPLString osPropertyName;
1516 : osPropertyName.Printf("%s.%s", poClass->GetName(),
1517 94 : poProperty->GetName());
1518 188 : CPLString osSrcElement;
1519 : osSrcElement.Printf("member|%s|%s", poClass->GetName(),
1520 94 : poProperty->GetSrcElement());
1521 : GMLGeometryPropertyDefn *poNewProperty =
1522 : new GMLGeometryPropertyDefn(osPropertyName, osSrcElement,
1523 94 : poProperty->GetType(), -1,
1524 188 : poProperty->IsNullable());
1525 94 : poJointClass->AddGeometryProperty(poNewProperty);
1526 : }
1527 : }
1528 47 : poJointClass->SetSchemaLocked(true);
1529 :
1530 47 : poReader->ClearClasses();
1531 47 : poReader->AddClass(poJointClass);
1532 47 : }
1533 :
1534 : /************************************************************************/
1535 : /* BuildJointClassFromScannedSchema() */
1536 : /************************************************************************/
1537 :
1538 2 : void OGRGMLDataSource::BuildJointClassFromScannedSchema()
1539 : {
1540 : // Make sure that all properties of a same base feature type are
1541 : // consecutive. If not, reorder.
1542 4 : std::vector<std::vector<GMLPropertyDefn *>> aapoProps;
1543 2 : GMLFeatureClass *poClass = poReader->GetClass(0);
1544 4 : CPLString osJointClassName = "join";
1545 :
1546 14 : for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
1547 : {
1548 12 : GMLPropertyDefn *poProp = poClass->GetProperty(iField);
1549 24 : CPLString osPrefix(poProp->GetName());
1550 12 : size_t iPos = osPrefix.find('.');
1551 12 : if (iPos != std::string::npos)
1552 12 : osPrefix.resize(iPos);
1553 12 : int iSubClass = 0; // Used after for.
1554 18 : for (; iSubClass < static_cast<int>(aapoProps.size()); iSubClass++)
1555 : {
1556 14 : CPLString osPrefixClass(aapoProps[iSubClass][0]->GetName());
1557 14 : iPos = osPrefixClass.find('.');
1558 14 : if (iPos != std::string::npos)
1559 14 : osPrefixClass.resize(iPos);
1560 14 : if (osPrefix == osPrefixClass)
1561 8 : break;
1562 : }
1563 12 : if (iSubClass == static_cast<int>(aapoProps.size()))
1564 : {
1565 4 : osJointClassName += "_";
1566 4 : osJointClassName += osPrefix;
1567 4 : aapoProps.push_back(std::vector<GMLPropertyDefn *>());
1568 : }
1569 12 : aapoProps[iSubClass].push_back(poProp);
1570 : }
1571 2 : poClass->SetElementName(poClass->GetName());
1572 2 : poClass->SetName(osJointClassName);
1573 :
1574 2 : poClass->StealProperties();
1575 : std::vector<std::pair<CPLString, std::vector<GMLGeometryPropertyDefn *>>>
1576 4 : aapoGeomProps;
1577 6 : for (int iSubClass = 0; iSubClass < static_cast<int>(aapoProps.size());
1578 : iSubClass++)
1579 : {
1580 8 : CPLString osPrefixClass(aapoProps[iSubClass][0]->GetName());
1581 4 : size_t iPos = osPrefixClass.find('.');
1582 4 : if (iPos != std::string::npos)
1583 4 : osPrefixClass.resize(iPos);
1584 : aapoGeomProps.emplace_back(
1585 4 : std::pair(osPrefixClass, std::vector<GMLGeometryPropertyDefn *>()));
1586 16 : for (int iField = 0;
1587 16 : iField < static_cast<int>(aapoProps[iSubClass].size()); iField++)
1588 : {
1589 12 : poClass->AddProperty(aapoProps[iSubClass][iField]);
1590 : }
1591 : }
1592 2 : aapoProps.resize(0);
1593 :
1594 : // Reorder geometry fields too
1595 6 : for (int iField = 0; iField < poClass->GetGeometryPropertyCount(); iField++)
1596 : {
1597 4 : GMLGeometryPropertyDefn *poProp = poClass->GetGeometryProperty(iField);
1598 8 : CPLString osPrefix(poProp->GetName());
1599 4 : size_t iPos = osPrefix.find('.');
1600 4 : if (iPos != std::string::npos)
1601 4 : osPrefix.resize(iPos);
1602 4 : int iSubClass = 0; // Used after for.
1603 6 : for (; iSubClass < static_cast<int>(aapoGeomProps.size()); iSubClass++)
1604 : {
1605 6 : if (osPrefix == aapoGeomProps[iSubClass].first)
1606 4 : break;
1607 : }
1608 4 : if (iSubClass == static_cast<int>(aapoGeomProps.size()))
1609 : aapoGeomProps.emplace_back(
1610 0 : std::pair(osPrefix, std::vector<GMLGeometryPropertyDefn *>()));
1611 4 : aapoGeomProps[iSubClass].second.push_back(poProp);
1612 : }
1613 2 : poClass->StealGeometryProperties();
1614 6 : for (int iSubClass = 0; iSubClass < static_cast<int>(aapoGeomProps.size());
1615 : iSubClass++)
1616 : {
1617 8 : for (int iField = 0;
1618 8 : iField < static_cast<int>(aapoGeomProps[iSubClass].second.size());
1619 : iField++)
1620 : {
1621 4 : poClass->AddGeometryProperty(
1622 4 : aapoGeomProps[iSubClass].second[iField]);
1623 : }
1624 : }
1625 2 : }
1626 :
1627 : /************************************************************************/
1628 : /* TranslateGMLSchema() */
1629 : /************************************************************************/
1630 :
1631 556 : OGRGMLLayer *OGRGMLDataSource::TranslateGMLSchema(GMLFeatureClass *poClass)
1632 :
1633 : {
1634 : // Create an empty layer.
1635 556 : const char *pszSRSName = poClass->GetSRSName();
1636 556 : OGRSpatialReference *poSRS = nullptr;
1637 556 : if (pszSRSName)
1638 : {
1639 118 : poSRS = new OGRSpatialReference();
1640 118 : poSRS->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
1641 : ? OAMS_TRADITIONAL_GIS_ORDER
1642 : : OAMS_AUTHORITY_COMPLIANT);
1643 118 : if (poSRS->SetFromUserInput(
1644 : pszSRSName,
1645 118 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
1646 : OGRERR_NONE)
1647 : {
1648 0 : delete poSRS;
1649 0 : poSRS = nullptr;
1650 : }
1651 : }
1652 : else
1653 : {
1654 438 : pszSRSName = GetGlobalSRSName();
1655 :
1656 438 : if (pszSRSName && GML_IsLegitSRSName(pszSRSName))
1657 : {
1658 35 : poSRS = new OGRSpatialReference();
1659 35 : poSRS->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
1660 : ? OAMS_TRADITIONAL_GIS_ORDER
1661 : : OAMS_AUTHORITY_COMPLIANT);
1662 35 : if (poSRS->SetFromUserInput(
1663 : pszSRSName,
1664 : OGRSpatialReference::
1665 35 : SET_FROM_USER_INPUT_LIMITATIONS_get()) != OGRERR_NONE)
1666 : {
1667 0 : delete poSRS;
1668 0 : poSRS = nullptr;
1669 : }
1670 :
1671 70 : if (poSRS != nullptr && m_bInvertAxisOrderIfLatLong &&
1672 35 : GML_IsSRSLatLongOrder(pszSRSName))
1673 : {
1674 23 : if (!poClass->HasExtents() && sBoundingRect.IsInit())
1675 : {
1676 23 : poClass->SetExtents(sBoundingRect.MinY, sBoundingRect.MaxY,
1677 : sBoundingRect.MinX, sBoundingRect.MaxX);
1678 : }
1679 : }
1680 : }
1681 :
1682 438 : if (!poClass->HasExtents() && sBoundingRect.IsInit())
1683 : {
1684 12 : poClass->SetExtents(sBoundingRect.MinX, sBoundingRect.MaxX,
1685 : sBoundingRect.MinY, sBoundingRect.MaxY);
1686 : }
1687 : }
1688 :
1689 : // Report a COMPD_CS only if GML_REPORT_COMPD_CS is explicitly set to TRUE.
1690 556 : if (poSRS != nullptr && poSRS->IsCompound())
1691 : {
1692 : const char *pszReportCompdCS =
1693 8 : CPLGetConfigOption("GML_REPORT_COMPD_CS", nullptr);
1694 8 : if (pszReportCompdCS == nullptr)
1695 : {
1696 8 : CPLDebug("GML", "Compound CRS detected but only horizontal part "
1697 : "will be reported. Set the GML_REPORT_COMPD_CS=YES "
1698 : "configuration option to get the Compound CRS");
1699 8 : pszReportCompdCS = "FALSE";
1700 : }
1701 8 : if (!CPLTestBool(pszReportCompdCS))
1702 : {
1703 8 : OGR_SRSNode *poCOMPD_CS = poSRS->GetAttrNode("COMPD_CS");
1704 8 : if (poCOMPD_CS != nullptr)
1705 : {
1706 8 : OGR_SRSNode *poCandidateRoot = poCOMPD_CS->GetNode("PROJCS");
1707 8 : if (poCandidateRoot == nullptr)
1708 1 : poCandidateRoot = poCOMPD_CS->GetNode("GEOGCS");
1709 8 : if (poCandidateRoot != nullptr)
1710 : {
1711 8 : poSRS->SetRoot(poCandidateRoot->Clone());
1712 : }
1713 : }
1714 : }
1715 : }
1716 :
1717 556 : OGRGMLLayer *poLayer = new OGRGMLLayer(poClass->GetName(), false, this);
1718 :
1719 : // Added attributes (properties).
1720 556 : if (bExposeGMLId)
1721 : {
1722 822 : OGRFieldDefn oField("gml_id", OFTString);
1723 411 : oField.SetNullable(FALSE);
1724 411 : poLayer->GetLayerDefn()->AddFieldDefn(&oField);
1725 : }
1726 145 : else if (bExposeFid)
1727 : {
1728 110 : OGRFieldDefn oField("fid", OFTString);
1729 55 : oField.SetNullable(FALSE);
1730 55 : poLayer->GetLayerDefn()->AddFieldDefn(&oField);
1731 : }
1732 :
1733 1171 : for (int iField = 0; iField < poClass->GetGeometryPropertyCount(); iField++)
1734 : {
1735 : GMLGeometryPropertyDefn *poProperty =
1736 615 : poClass->GetGeometryProperty(iField);
1737 :
1738 : // Patch wrong .gfs file produced by earlier versions
1739 615 : if (poProperty->GetType() == wkbPolyhedralSurface &&
1740 0 : strcmp(poProperty->GetName(), "lod2Solid") == 0)
1741 : {
1742 0 : poProperty->SetType(wkbPolyhedralSurfaceZ);
1743 : }
1744 :
1745 1230 : OGRGeomFieldDefn oField(poProperty->GetName(), poProperty->GetType());
1746 1036 : if (poClass->GetGeometryPropertyCount() == 1 &&
1747 421 : poClass->GetFeatureCount() == 0)
1748 : {
1749 0 : oField.SetType(wkbUnknown);
1750 : }
1751 :
1752 615 : const auto &osSRSName = poProperty->GetSRSName();
1753 615 : if (!osSRSName.empty())
1754 : {
1755 30 : OGRSpatialReference *poSRS2 = new OGRSpatialReference();
1756 30 : poSRS2->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
1757 : ? OAMS_TRADITIONAL_GIS_ORDER
1758 : : OAMS_AUTHORITY_COMPLIANT);
1759 30 : if (poSRS2->SetFromUserInput(
1760 : osSRSName.c_str(),
1761 : OGRSpatialReference::
1762 30 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
1763 : {
1764 30 : oField.SetSpatialRef(poSRS2);
1765 : }
1766 30 : poSRS2->Release();
1767 : }
1768 : else
1769 : {
1770 585 : oField.SetSpatialRef(poSRS);
1771 : }
1772 615 : oField.SetNullable(poProperty->IsNullable());
1773 615 : oField.SetCoordinatePrecision(poProperty->GetCoordinatePrecision());
1774 615 : poLayer->GetLayerDefn()->AddGeomFieldDefn(&oField);
1775 : }
1776 :
1777 556 : if (poReader->GetClassCount() == 1)
1778 : {
1779 326 : int iInsertPos = 0;
1780 331 : for (const auto &osElt : m_aosGMLExtraElements)
1781 : {
1782 : GMLPropertyDefn *poProperty =
1783 5 : new GMLPropertyDefn(osElt.c_str(), osElt.c_str());
1784 5 : poProperty->SetType(GMLPT_String);
1785 5 : if (poClass->AddProperty(poProperty, iInsertPos) == iInsertPos)
1786 3 : ++iInsertPos;
1787 : else
1788 2 : delete poProperty;
1789 : }
1790 : }
1791 :
1792 2709 : for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
1793 : {
1794 2153 : GMLPropertyDefn *poProperty = poClass->GetProperty(iField);
1795 2153 : OGRFieldSubType eSubType = poProperty->GetSubType();
1796 : const OGRFieldType eFType =
1797 2153 : GML_GetOGRFieldType(poProperty->GetType(), eSubType);
1798 4306 : OGRFieldDefn oField(poProperty->GetName(), eFType);
1799 2153 : oField.SetSubType(eSubType);
1800 2153 : if (STARTS_WITH_CI(oField.GetNameRef(), "ogr:"))
1801 0 : oField.SetName(poProperty->GetName() + 4);
1802 2153 : if (poProperty->GetWidth() > 0)
1803 640 : oField.SetWidth(poProperty->GetWidth());
1804 2153 : if (poProperty->GetPrecision() > 0)
1805 45 : oField.SetPrecision(poProperty->GetPrecision());
1806 2153 : if (!bEmptyAsNull)
1807 2 : oField.SetNullable(poProperty->IsNullable());
1808 2153 : oField.SetUnique(poProperty->IsUnique());
1809 2153 : oField.SetComment(poProperty->GetDocumentation());
1810 2153 : poLayer->GetLayerDefn()->AddFieldDefn(&oField);
1811 : }
1812 :
1813 556 : if (poSRS != nullptr)
1814 153 : poSRS->Release();
1815 :
1816 556 : return poLayer;
1817 : }
1818 :
1819 : /************************************************************************/
1820 : /* GetGlobalSRSName() */
1821 : /************************************************************************/
1822 :
1823 1059 : const char *OGRGMLDataSource::GetGlobalSRSName()
1824 : {
1825 1059 : if (poReader->CanUseGlobalSRSName() || bUseGlobalSRSName)
1826 486 : return poReader->GetGlobalSRSName();
1827 : else
1828 573 : return nullptr;
1829 : }
1830 :
1831 : /************************************************************************/
1832 : /* Create() */
1833 : /************************************************************************/
1834 :
1835 101 : bool OGRGMLDataSource::Create(const char *pszFilename, char **papszOptions)
1836 :
1837 : {
1838 101 : if (fpOutput != nullptr || poReader != nullptr)
1839 : {
1840 0 : CPLAssert(false);
1841 : return false;
1842 : }
1843 :
1844 101 : if (strcmp(pszFilename, "/dev/stdout") == 0)
1845 0 : pszFilename = "/vsistdout/";
1846 :
1847 : // Read options.
1848 101 : CSLDestroy(papszCreateOptions);
1849 101 : papszCreateOptions = CSLDuplicate(papszOptions);
1850 :
1851 : const char *pszFormat =
1852 101 : CSLFetchNameValueDef(papszCreateOptions, "FORMAT", "GML3.2");
1853 101 : bIsOutputGML3 = EQUAL(pszFormat, "GML3");
1854 101 : bIsOutputGML3Deegree = EQUAL(pszFormat, "GML3Deegree");
1855 101 : bIsOutputGML32 = EQUAL(pszFormat, "GML3.2");
1856 101 : if (bIsOutputGML3Deegree || bIsOutputGML32)
1857 79 : bIsOutputGML3 = true;
1858 :
1859 101 : eSRSNameFormat = (bIsOutputGML3) ? SRSNAME_OGC_URN : SRSNAME_SHORT;
1860 101 : if (bIsOutputGML3)
1861 : {
1862 : const char *pszLongSRS =
1863 91 : CSLFetchNameValue(papszCreateOptions, "GML3_LONGSRS");
1864 : const char *pszSRSNameFormat =
1865 91 : CSLFetchNameValue(papszCreateOptions, "SRSNAME_FORMAT");
1866 91 : if (pszSRSNameFormat)
1867 : {
1868 6 : if (pszLongSRS)
1869 : {
1870 0 : CPLError(CE_Warning, CPLE_NotSupported,
1871 : "Both GML3_LONGSRS and SRSNAME_FORMAT specified. "
1872 : "Ignoring GML3_LONGSRS");
1873 : }
1874 6 : if (EQUAL(pszSRSNameFormat, "SHORT"))
1875 1 : eSRSNameFormat = SRSNAME_SHORT;
1876 5 : else if (EQUAL(pszSRSNameFormat, "OGC_URN"))
1877 1 : eSRSNameFormat = SRSNAME_OGC_URN;
1878 4 : else if (EQUAL(pszSRSNameFormat, "OGC_URL"))
1879 4 : eSRSNameFormat = SRSNAME_OGC_URL;
1880 : else
1881 : {
1882 0 : CPLError(CE_Warning, CPLE_NotSupported,
1883 : "Invalid value for SRSNAME_FORMAT. "
1884 : "Using SRSNAME_OGC_URN");
1885 : }
1886 : }
1887 85 : else if (pszLongSRS && !CPLTestBool(pszLongSRS))
1888 0 : eSRSNameFormat = SRSNAME_SHORT;
1889 : }
1890 :
1891 101 : bWriteSpaceIndentation = CPLTestBool(
1892 101 : CSLFetchNameValueDef(papszCreateOptions, "SPACE_INDENTATION", "YES"));
1893 :
1894 : // Create the output file.
1895 101 : osFilename = pszFilename;
1896 101 : SetDescription(pszFilename);
1897 :
1898 101 : if (strcmp(pszFilename, "/vsistdout/") == 0 ||
1899 101 : STARTS_WITH(pszFilename, "/vsigzip/"))
1900 : {
1901 0 : fpOutput = VSIFOpenExL(pszFilename, "wb", true);
1902 0 : bFpOutputIsNonSeekable = true;
1903 0 : bFpOutputSingleFile = true;
1904 : }
1905 101 : else if (STARTS_WITH(pszFilename, "/vsizip/"))
1906 : {
1907 0 : if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "zip"))
1908 : {
1909 0 : SetDescription(
1910 0 : CPLFormFilenameSafe(pszFilename, "out.gml", nullptr).c_str());
1911 : }
1912 :
1913 0 : fpOutput = VSIFOpenExL(GetDescription(), "wb", true);
1914 0 : bFpOutputIsNonSeekable = true;
1915 : }
1916 : else
1917 101 : fpOutput = VSIFOpenExL(pszFilename, "wb+", true);
1918 101 : if (fpOutput == nullptr)
1919 : {
1920 1 : CPLError(CE_Failure, CPLE_OpenFailed,
1921 : "Failed to create GML file %s: %s", pszFilename,
1922 : VSIGetLastErrorMsg());
1923 1 : return false;
1924 : }
1925 :
1926 : // Write out "standard" header.
1927 100 : PrintLine(fpOutput, "%s", "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
1928 :
1929 100 : if (!bFpOutputIsNonSeekable)
1930 100 : nSchemaInsertLocation = static_cast<int>(VSIFTellL(fpOutput));
1931 :
1932 100 : const char *pszPrefix = GetAppPrefix();
1933 100 : const char *pszTargetNameSpace = CSLFetchNameValueDef(
1934 : papszOptions, "TARGET_NAMESPACE", "http://ogr.maptools.org/");
1935 :
1936 100 : if (GMLFeatureCollection())
1937 1 : PrintLine(fpOutput, "<gml:FeatureCollection");
1938 99 : else if (RemoveAppPrefix())
1939 2 : PrintLine(fpOutput, "<FeatureCollection");
1940 : else
1941 97 : PrintLine(fpOutput, "<%s:FeatureCollection", pszPrefix);
1942 :
1943 100 : if (IsGML32Output())
1944 : {
1945 77 : char *pszGMLId = CPLEscapeString(
1946 : CSLFetchNameValueDef(papszOptions, "GML_ID", "aFeatureCollection"),
1947 : -1, CPLES_XML);
1948 77 : PrintLine(fpOutput, " gml:id=\"%s\"", pszGMLId);
1949 77 : CPLFree(pszGMLId);
1950 : }
1951 :
1952 : // Write out schema info if provided in creation options.
1953 100 : const char *pszSchemaURI = CSLFetchNameValue(papszOptions, "XSISCHEMAURI");
1954 100 : const char *pszSchemaOpt = CSLFetchNameValue(papszOptions, "XSISCHEMA");
1955 :
1956 100 : if (pszSchemaURI != nullptr)
1957 : {
1958 0 : PrintLine(
1959 : fpOutput,
1960 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
1961 0 : PrintLine(fpOutput, " xsi:schemaLocation=\"%s\"", pszSchemaURI);
1962 : }
1963 100 : else if (pszSchemaOpt == nullptr || EQUAL(pszSchemaOpt, "EXTERNAL"))
1964 : {
1965 : char *pszBasename =
1966 98 : CPLStrdup(CPLGetBasenameSafe(GetDescription()).c_str());
1967 :
1968 98 : PrintLine(
1969 : fpOutput,
1970 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
1971 98 : PrintLine(fpOutput, " xsi:schemaLocation=\"%s %s\"",
1972 : pszTargetNameSpace,
1973 196 : CPLResetExtensionSafe(pszBasename, "xsd").c_str());
1974 98 : CPLFree(pszBasename);
1975 : }
1976 :
1977 100 : if (RemoveAppPrefix())
1978 2 : PrintLine(fpOutput, " xmlns=\"%s\"", pszTargetNameSpace);
1979 : else
1980 98 : PrintLine(fpOutput, " xmlns:%s=\"%s\"", pszPrefix,
1981 : pszTargetNameSpace);
1982 :
1983 100 : if (IsGML32Output())
1984 77 : PrintLine(fpOutput, "%s",
1985 : " xmlns:gml=\"http://www.opengis.net/gml/3.2\">");
1986 : else
1987 23 : PrintLine(fpOutput, "%s",
1988 : " xmlns:gml=\"http://www.opengis.net/gml\">");
1989 :
1990 100 : return true;
1991 : }
1992 :
1993 : /************************************************************************/
1994 : /* WriteTopElements() */
1995 : /************************************************************************/
1996 :
1997 100 : void OGRGMLDataSource::WriteTopElements()
1998 : {
1999 300 : const char *pszDescription = CSLFetchNameValueDef(
2000 100 : papszCreateOptions, "DESCRIPTION", GetMetadataItem("DESCRIPTION"));
2001 100 : if (pszDescription != nullptr)
2002 : {
2003 2 : if (bWriteSpaceIndentation)
2004 2 : VSIFPrintfL(fpOutput, " ");
2005 2 : char *pszTmp = CPLEscapeString(pszDescription, -1, CPLES_XML);
2006 2 : PrintLine(fpOutput, "<gml:description>%s</gml:description>", pszTmp);
2007 2 : CPLFree(pszTmp);
2008 : }
2009 :
2010 100 : const char *l_pszName = CSLFetchNameValueDef(papszCreateOptions, "NAME",
2011 100 : GetMetadataItem("NAME"));
2012 100 : if (l_pszName != nullptr)
2013 : {
2014 2 : if (bWriteSpaceIndentation)
2015 2 : VSIFPrintfL(fpOutput, " ");
2016 2 : char *pszTmp = CPLEscapeString(l_pszName, -1, CPLES_XML);
2017 2 : PrintLine(fpOutput, "<gml:name>%s</gml:name>", pszTmp);
2018 2 : CPLFree(pszTmp);
2019 : }
2020 :
2021 : // Should we initialize an area to place the boundedBy element?
2022 : // We will need to seek back to fill it in.
2023 100 : nBoundedByLocation = -1;
2024 100 : if (CPLFetchBool(papszCreateOptions, "BOUNDEDBY", true))
2025 : {
2026 100 : if (!bFpOutputIsNonSeekable)
2027 : {
2028 100 : nBoundedByLocation = static_cast<int>(VSIFTellL(fpOutput));
2029 :
2030 100 : if (nBoundedByLocation != -1)
2031 100 : PrintLine(fpOutput, "%350s", "");
2032 : }
2033 : else
2034 : {
2035 0 : if (bWriteSpaceIndentation)
2036 0 : VSIFPrintfL(fpOutput, " ");
2037 0 : if (IsGML3Output())
2038 0 : PrintLine(fpOutput,
2039 : "<gml:boundedBy><gml:Null /></gml:boundedBy>");
2040 : else
2041 0 : PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing</"
2042 : "gml:null></gml:boundedBy>");
2043 : }
2044 : }
2045 100 : }
2046 :
2047 : /************************************************************************/
2048 : /* DealWithOgrSchemaOpenOption() */
2049 : /************************************************************************/
2050 :
2051 430 : bool OGRGMLDataSource::DealWithOgrSchemaOpenOption(
2052 : const GDALOpenInfo *poOpenInfo)
2053 : {
2054 :
2055 : std::string osFieldsSchemaOverrideParam =
2056 860 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "OGR_SCHEMA", "");
2057 :
2058 430 : if (!osFieldsSchemaOverrideParam.empty())
2059 : {
2060 :
2061 : // GML driver does not support update at the moment so this will never happen
2062 26 : if (poOpenInfo->eAccess == GA_Update)
2063 : {
2064 0 : CPLError(CE_Failure, CPLE_NotSupported,
2065 : "OGR_SCHEMA open option is not supported in update mode.");
2066 10 : return false;
2067 : }
2068 :
2069 26 : OGRSchemaOverride osSchemaOverride;
2070 50 : if (!osSchemaOverride.LoadFromJSON(osFieldsSchemaOverrideParam) ||
2071 24 : !osSchemaOverride.IsValid())
2072 : {
2073 2 : return false;
2074 : }
2075 :
2076 24 : const auto &oLayerOverrides = osSchemaOverride.GetLayerOverrides();
2077 42 : for (const auto &oLayer : oLayerOverrides)
2078 : {
2079 26 : const auto &oLayerName = oLayer.first;
2080 26 : const auto &oLayerFieldOverride = oLayer.second;
2081 26 : const bool bIsFullOverride{oLayerFieldOverride.IsFullOverride()};
2082 26 : std::vector<GMLPropertyDefn *> aoProperties;
2083 :
2084 26 : CPLDebug("GML", "Applying schema override for layer %s",
2085 : oLayerName.c_str());
2086 :
2087 : // Fail if the layer name does not exist
2088 26 : const auto oClass = poReader->GetClass(oLayerName.c_str());
2089 26 : if (oClass == nullptr)
2090 : {
2091 6 : CPLError(CE_Failure, CPLE_AppDefined, "Layer %s not found",
2092 : oLayerName.c_str());
2093 6 : return false;
2094 : }
2095 :
2096 20 : const auto &oLayerFields = oLayerFieldOverride.GetFieldOverrides();
2097 42 : for (const auto &oFieldOverrideIt : oLayerFields)
2098 : {
2099 : const auto oProperty =
2100 24 : oClass->GetProperty(oFieldOverrideIt.first.c_str());
2101 24 : if (oProperty == nullptr)
2102 : {
2103 2 : CPLError(CE_Failure, CPLE_AppDefined,
2104 : "Field %s not found in layer %s",
2105 : oFieldOverrideIt.first.c_str(),
2106 : oLayerName.c_str());
2107 2 : return false;
2108 : }
2109 :
2110 22 : const auto &oFieldOverride = oFieldOverrideIt.second;
2111 :
2112 : const OGRFieldSubType eSubType =
2113 22 : oFieldOverride.GetFieldSubType().value_or(OFSTNone);
2114 :
2115 22 : if (oFieldOverride.GetFieldSubType().has_value())
2116 : {
2117 8 : oProperty->SetSubType(eSubType);
2118 : }
2119 :
2120 22 : if (oFieldOverride.GetFieldType().has_value())
2121 : {
2122 12 : oProperty->SetType(GML_FromOGRFieldType(
2123 24 : oFieldOverride.GetFieldType().value(), eSubType));
2124 : }
2125 :
2126 22 : if (oFieldOverride.GetFieldName().has_value())
2127 : {
2128 4 : oProperty->SetName(
2129 8 : oFieldOverride.GetFieldName().value().c_str());
2130 : }
2131 :
2132 22 : if (oFieldOverride.GetFieldWidth().has_value())
2133 : {
2134 2 : oProperty->SetWidth(oFieldOverride.GetFieldWidth().value());
2135 : }
2136 :
2137 22 : if (oFieldOverride.GetFieldPrecision().has_value())
2138 : {
2139 4 : oProperty->SetPrecision(
2140 2 : oFieldOverride.GetFieldPrecision().value());
2141 : }
2142 :
2143 22 : if (bIsFullOverride)
2144 : {
2145 4 : aoProperties.push_back(oProperty);
2146 : }
2147 : }
2148 :
2149 : // Remove fields not in the override
2150 20 : if (bIsFullOverride &&
2151 2 : aoProperties.size() !=
2152 2 : static_cast<size_t>(oClass->GetPropertyCount()))
2153 : {
2154 8 : for (int j = 0; j < oClass->GetPropertyCount(); ++j)
2155 : {
2156 6 : const auto oProperty = oClass->GetProperty(j);
2157 6 : if (std::find(aoProperties.begin(), aoProperties.end(),
2158 6 : oProperty) == aoProperties.end())
2159 : {
2160 2 : delete (oProperty);
2161 : }
2162 : }
2163 :
2164 2 : oClass->StealProperties();
2165 :
2166 6 : for (const auto &oProperty : aoProperties)
2167 : {
2168 4 : oClass->AddProperty(oProperty);
2169 : }
2170 : }
2171 : }
2172 : }
2173 420 : return true;
2174 : }
2175 :
2176 : /************************************************************************/
2177 : /* DeclareNewWriteSRS() */
2178 : /************************************************************************/
2179 :
2180 : // Check that all SRS passed to ICreateLayer() and CreateGeomField()
2181 : // are the same (or all null)
2182 :
2183 124 : void OGRGMLDataSource::DeclareNewWriteSRS(const OGRSpatialReference *poSRS)
2184 : {
2185 124 : if (m_bWriteGlobalSRS)
2186 : {
2187 124 : if (!m_bWriteGlobalSRSInit)
2188 : {
2189 91 : m_bWriteGlobalSRSInit = true;
2190 91 : if (poSRS)
2191 : {
2192 27 : m_poWriteGlobalSRS.reset(poSRS->Clone());
2193 27 : m_poWriteGlobalSRS->SetAxisMappingStrategy(
2194 : OAMS_TRADITIONAL_GIS_ORDER);
2195 : }
2196 : }
2197 : else
2198 : {
2199 33 : if (m_poWriteGlobalSRS)
2200 : {
2201 3 : const char *const apszOptions[] = {
2202 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
2203 5 : if (!poSRS ||
2204 2 : !poSRS->IsSame(m_poWriteGlobalSRS.get(), apszOptions))
2205 : {
2206 2 : m_bWriteGlobalSRS = false;
2207 : }
2208 : }
2209 : else
2210 : {
2211 30 : if (poSRS)
2212 1 : m_bWriteGlobalSRS = false;
2213 : }
2214 : }
2215 : }
2216 124 : }
2217 :
2218 : /************************************************************************/
2219 : /* ICreateLayer() */
2220 : /************************************************************************/
2221 :
2222 : OGRLayer *
2223 126 : OGRGMLDataSource::ICreateLayer(const char *pszLayerName,
2224 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
2225 : CSLConstList /*papszOptions*/)
2226 : {
2227 : // Verify we are in update mode.
2228 126 : if (fpOutput == nullptr)
2229 : {
2230 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
2231 : "Data source %s opened for read access.\n"
2232 : "New layer %s cannot be created.\n",
2233 0 : GetDescription(), pszLayerName);
2234 :
2235 0 : return nullptr;
2236 : }
2237 :
2238 : const auto eType =
2239 126 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
2240 : const auto poSRS =
2241 126 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
2242 :
2243 : // Ensure name is safe as an element name.
2244 126 : char *pszCleanLayerName = CPLStrdup(pszLayerName);
2245 :
2246 126 : CPLCleanXMLElementName(pszCleanLayerName);
2247 126 : if (strcmp(pszCleanLayerName, pszLayerName) != 0)
2248 : {
2249 0 : CPLError(CE_Warning, CPLE_AppDefined,
2250 : "Layer name '%s' adjusted to '%s' for XML validity.",
2251 : pszLayerName, pszCleanLayerName);
2252 : }
2253 :
2254 126 : if (nLayers == 0)
2255 : {
2256 97 : WriteTopElements();
2257 : }
2258 :
2259 : // Create the layer object.
2260 126 : OGRGMLLayer *poLayer = new OGRGMLLayer(pszCleanLayerName, true, this);
2261 126 : poLayer->GetLayerDefn()->SetGeomType(eType);
2262 126 : if (eType != wkbNone)
2263 : {
2264 105 : auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0);
2265 105 : const char *pszGeomFieldName = poSrcGeomFieldDefn->GetNameRef();
2266 105 : if (!pszGeomFieldName || pszGeomFieldName[0] == 0)
2267 104 : pszGeomFieldName = "geometryProperty";
2268 105 : poGeomFieldDefn->SetName(pszGeomFieldName);
2269 105 : poGeomFieldDefn->SetNullable(poSrcGeomFieldDefn->IsNullable());
2270 105 : DeclareNewWriteSRS(poSRS);
2271 105 : if (poSRS != nullptr)
2272 : {
2273 24 : auto poSRSClone = poSRS->Clone();
2274 24 : poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2275 24 : poGeomFieldDefn->SetSpatialRef(poSRSClone);
2276 24 : poSRSClone->Dereference();
2277 : }
2278 105 : poGeomFieldDefn->SetCoordinatePrecision(
2279 : poSrcGeomFieldDefn->GetCoordinatePrecision());
2280 : }
2281 :
2282 126 : CPLFree(pszCleanLayerName);
2283 :
2284 : // Add layer to data source layer list.
2285 126 : papoLayers = static_cast<OGRLayer **>(
2286 126 : CPLRealloc(papoLayers, sizeof(OGRLayer *) * (nLayers + 1)));
2287 :
2288 126 : papoLayers[nLayers++] = poLayer;
2289 :
2290 126 : return poLayer;
2291 : }
2292 :
2293 : /************************************************************************/
2294 : /* TestCapability() */
2295 : /************************************************************************/
2296 :
2297 172 : int OGRGMLDataSource::TestCapability(const char *pszCap)
2298 :
2299 : {
2300 172 : if (EQUAL(pszCap, ODsCCreateLayer))
2301 68 : return TRUE;
2302 104 : else if (EQUAL(pszCap, ODsCCreateGeomFieldAfterCreateLayer))
2303 42 : return TRUE;
2304 62 : else if (EQUAL(pszCap, ODsCCurveGeometries))
2305 5 : return bIsOutputGML3;
2306 57 : else if (EQUAL(pszCap, ODsCZGeometries))
2307 2 : return TRUE;
2308 55 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
2309 0 : return TRUE;
2310 : else
2311 55 : return FALSE;
2312 : }
2313 :
2314 : /************************************************************************/
2315 : /* GetLayer() */
2316 : /************************************************************************/
2317 :
2318 860 : OGRLayer *OGRGMLDataSource::GetLayer(int iLayer)
2319 :
2320 : {
2321 860 : if (iLayer < 0 || iLayer >= nLayers)
2322 7 : return nullptr;
2323 : else
2324 853 : return papoLayers[iLayer];
2325 : }
2326 :
2327 : /************************************************************************/
2328 : /* GrowExtents() */
2329 : /************************************************************************/
2330 :
2331 211 : void OGRGMLDataSource::GrowExtents(OGREnvelope3D *psGeomBounds,
2332 : int nCoordDimension)
2333 :
2334 : {
2335 211 : sBoundingRect.Merge(*psGeomBounds);
2336 211 : if (nCoordDimension == 3)
2337 46 : bBBOX3D = true;
2338 211 : }
2339 :
2340 : /************************************************************************/
2341 : /* InsertHeader() */
2342 : /* */
2343 : /* This method is used to update boundedby info for a */
2344 : /* dataset, and insert schema descriptions depending on */
2345 : /* selection options in effect. */
2346 : /************************************************************************/
2347 :
2348 99 : void OGRGMLDataSource::InsertHeader()
2349 :
2350 : {
2351 99 : int nSchemaStart = 0;
2352 :
2353 99 : if (bFpOutputSingleFile)
2354 0 : return;
2355 :
2356 : // Do we want to write the schema within the GML instance doc
2357 : // or to a separate file? For now we only support external.
2358 : const char *pszSchemaURI =
2359 99 : CSLFetchNameValue(papszCreateOptions, "XSISCHEMAURI");
2360 : const char *pszSchemaOpt =
2361 99 : CSLFetchNameValue(papszCreateOptions, "XSISCHEMA");
2362 :
2363 99 : const bool bGMLFeatureCollection = GMLFeatureCollection();
2364 :
2365 99 : if (pszSchemaURI != nullptr)
2366 0 : return;
2367 :
2368 99 : VSILFILE *fpSchema = nullptr;
2369 99 : if (pszSchemaOpt == nullptr || EQUAL(pszSchemaOpt, "EXTERNAL"))
2370 : {
2371 : const std::string l_osXSDFilename =
2372 98 : CPLResetExtensionSafe(GetDescription(), "xsd");
2373 :
2374 98 : fpSchema = VSIFOpenL(l_osXSDFilename.c_str(), "wt");
2375 98 : if (fpSchema == nullptr)
2376 : {
2377 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2378 : "Failed to open file %.500s for schema output.",
2379 : l_osXSDFilename.c_str());
2380 0 : return;
2381 : }
2382 196 : PrintLine(fpSchema, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
2383 : }
2384 1 : else if (EQUAL(pszSchemaOpt, "INTERNAL"))
2385 : {
2386 0 : if (fpOutput == nullptr)
2387 0 : return;
2388 0 : nSchemaStart = static_cast<int>(VSIFTellL(fpOutput));
2389 0 : fpSchema = fpOutput;
2390 : }
2391 : else
2392 : {
2393 1 : return;
2394 : }
2395 :
2396 : // Write the schema section at the end of the file. Once
2397 : // complete, we will read it back in, and then move the whole
2398 : // file "down" enough to insert the schema at the beginning.
2399 :
2400 : // Detect if there are fields of List types.
2401 98 : bool bHasListFields = false;
2402 :
2403 98 : const int nLayerCount = OGRGMLDataSource::GetLayerCount();
2404 223 : for (int iLayer = 0; !bHasListFields && iLayer < nLayerCount; iLayer++)
2405 : {
2406 125 : OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
2407 294 : for (int iField = 0;
2408 294 : !bHasListFields && iField < poFDefn->GetFieldCount(); iField++)
2409 : {
2410 169 : OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
2411 :
2412 336 : if (poFieldDefn->GetType() == OFTIntegerList ||
2413 334 : poFieldDefn->GetType() == OFTInteger64List ||
2414 503 : poFieldDefn->GetType() == OFTRealList ||
2415 167 : poFieldDefn->GetType() == OFTStringList)
2416 : {
2417 3 : bHasListFields = true;
2418 : }
2419 : }
2420 : }
2421 :
2422 : // Emit the start of the schema section.
2423 98 : const char *pszPrefix = GetAppPrefix();
2424 98 : if (pszPrefix[0] == '\0')
2425 0 : pszPrefix = "ogr";
2426 196 : const char *pszTargetNameSpace = CSLFetchNameValueDef(
2427 98 : papszCreateOptions, "TARGET_NAMESPACE", "http://ogr.maptools.org/");
2428 :
2429 98 : if (IsGML3Output())
2430 : {
2431 88 : PrintLine(fpSchema, "<xs:schema ");
2432 88 : PrintLine(fpSchema, " targetNamespace=\"%s\"", pszTargetNameSpace);
2433 88 : PrintLine(fpSchema, " xmlns:%s=\"%s\"", pszPrefix,
2434 : pszTargetNameSpace);
2435 88 : PrintLine(fpSchema,
2436 : " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"");
2437 88 : if (IsGML32Output())
2438 : {
2439 75 : PrintLine(fpSchema,
2440 : " xmlns:gml=\"http://www.opengis.net/gml/3.2\"");
2441 75 : if (!bGMLFeatureCollection)
2442 : {
2443 75 : PrintLine(
2444 : fpSchema,
2445 : " xmlns:gmlsf=\"http://www.opengis.net/gmlsf/2.0\"");
2446 : }
2447 : }
2448 : else
2449 : {
2450 13 : PrintLine(fpSchema, " xmlns:gml=\"http://www.opengis.net/gml\"");
2451 13 : if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
2452 : {
2453 11 : PrintLine(fpSchema,
2454 : " xmlns:gmlsf=\"http://www.opengis.net/gmlsf\"");
2455 : }
2456 : }
2457 88 : PrintLine(fpSchema, " elementFormDefault=\"qualified\"");
2458 88 : PrintLine(fpSchema, " version=\"1.0\">");
2459 :
2460 88 : if (IsGML32Output())
2461 : {
2462 75 : if (!bGMLFeatureCollection)
2463 : {
2464 75 : PrintLine(fpSchema, "<xs:annotation>");
2465 75 : PrintLine(fpSchema, " <xs:appinfo "
2466 : "source=\"http://schemas.opengis.net/"
2467 : "gmlsfProfile/2.0/gmlsfLevels.xsd\">");
2468 75 : PrintLine(
2469 : fpSchema,
2470 : " <gmlsf:ComplianceLevel>%d</gmlsf:ComplianceLevel>",
2471 : (bHasListFields) ? 1 : 0);
2472 75 : PrintLine(fpSchema, " </xs:appinfo>");
2473 75 : PrintLine(fpSchema, "</xs:annotation>");
2474 : }
2475 :
2476 75 : PrintLine(fpSchema,
2477 : "<xs:import namespace=\"http://www.opengis.net/gml/3.2\" "
2478 : "schemaLocation=\"http://schemas.opengis.net/gml/3.2.1/"
2479 : "gml.xsd\"/>");
2480 75 : if (!bGMLFeatureCollection)
2481 : {
2482 75 : PrintLine(
2483 : fpSchema,
2484 : "<xs:import namespace=\"http://www.opengis.net/gmlsf/2.0\" "
2485 : "schemaLocation=\"http://schemas.opengis.net/gmlsfProfile/"
2486 : "2.0/gmlsfLevels.xsd\"/>");
2487 : }
2488 : }
2489 : else
2490 : {
2491 13 : if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
2492 : {
2493 11 : PrintLine(fpSchema, "<xs:annotation>");
2494 11 : PrintLine(fpSchema,
2495 : " <xs:appinfo "
2496 : "source=\"http://schemas.opengis.net/gml/3.1.1/"
2497 : "profiles/gmlsfProfile/1.0.0/gmlsfLevels.xsd\">");
2498 11 : PrintLine(
2499 : fpSchema,
2500 : " <gmlsf:ComplianceLevel>%d</gmlsf:ComplianceLevel>",
2501 : (bHasListFields) ? 1 : 0);
2502 11 : PrintLine(fpSchema,
2503 : " "
2504 : "<gmlsf:GMLProfileSchema>http://schemas.opengis.net/"
2505 : "gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd</"
2506 : "gmlsf:GMLProfileSchema>");
2507 11 : PrintLine(fpSchema, " </xs:appinfo>");
2508 11 : PrintLine(fpSchema, "</xs:annotation>");
2509 : }
2510 :
2511 13 : PrintLine(fpSchema,
2512 : "<xs:import namespace=\"http://www.opengis.net/gml\" "
2513 : "schemaLocation=\"http://schemas.opengis.net/gml/3.1.1/"
2514 : "base/gml.xsd\"/>");
2515 13 : if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
2516 : {
2517 11 : PrintLine(
2518 : fpSchema,
2519 : "<xs:import namespace=\"http://www.opengis.net/gmlsf\" "
2520 : "schemaLocation=\"http://schemas.opengis.net/gml/3.1.1/"
2521 : "profiles/gmlsfProfile/1.0.0/gmlsfLevels.xsd\"/>");
2522 : }
2523 : }
2524 : }
2525 : else
2526 : {
2527 10 : PrintLine(fpSchema,
2528 : "<xs:schema targetNamespace=\"%s\" xmlns:%s=\"%s\" "
2529 : "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" "
2530 : "xmlns:gml=\"http://www.opengis.net/gml\" "
2531 : "elementFormDefault=\"qualified\" version=\"1.0\">",
2532 : pszTargetNameSpace, pszPrefix, pszTargetNameSpace);
2533 :
2534 10 : PrintLine(fpSchema,
2535 : "<xs:import namespace=\"http://www.opengis.net/gml\" "
2536 : "schemaLocation=\"http://schemas.opengis.net/gml/2.1.2/"
2537 : "feature.xsd\"/>");
2538 : }
2539 :
2540 : // Define the FeatureCollection element
2541 98 : if (!bGMLFeatureCollection)
2542 : {
2543 97 : bool bHasUniqueConstraints = false;
2544 221 : for (int iLayer = 0; (iLayer < nLayerCount) && !bHasUniqueConstraints;
2545 : iLayer++)
2546 : {
2547 124 : OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
2548 124 : const int nFieldCount = poFDefn->GetFieldCount();
2549 302 : for (int iField = 0;
2550 302 : (iField < nFieldCount) && !bHasUniqueConstraints; iField++)
2551 : {
2552 178 : const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
2553 178 : if (poFieldDefn->IsUnique())
2554 0 : bHasUniqueConstraints = true;
2555 : }
2556 : }
2557 :
2558 97 : const char *pszFeatureMemberPrefix = pszPrefix;
2559 97 : if (IsGML3Output())
2560 : {
2561 87 : if (IsGML32Output())
2562 : {
2563 : // GML Simple Features profile v2.0 mentions gml:AbstractGML as
2564 : // substitutionGroup but using gml:AbstractFeature makes it
2565 : // usablable by GMLJP2 v2.
2566 75 : PrintLine(fpSchema,
2567 : "<xs:element name=\"FeatureCollection\" "
2568 : "type=\"%s:FeatureCollectionType\" "
2569 : "substitutionGroup=\"gml:AbstractFeature\"%s>",
2570 : pszPrefix, bHasUniqueConstraints ? "" : "/");
2571 : }
2572 12 : else if (IsGML3DeegreeOutput())
2573 : {
2574 1 : PrintLine(fpSchema,
2575 : "<xs:element name=\"FeatureCollection\" "
2576 : "type=\"%s:FeatureCollectionType\" "
2577 : "substitutionGroup=\"gml:_FeatureCollection\"%s>",
2578 : pszPrefix, bHasUniqueConstraints ? "" : "/");
2579 : }
2580 : else
2581 : {
2582 11 : PrintLine(fpSchema,
2583 : "<xs:element name=\"FeatureCollection\" "
2584 : "type=\"%s:FeatureCollectionType\" "
2585 : "substitutionGroup=\"gml:_GML\"%s>",
2586 : pszPrefix, bHasUniqueConstraints ? "" : "/");
2587 : }
2588 : }
2589 : else
2590 : {
2591 10 : pszFeatureMemberPrefix = "gml";
2592 10 : PrintLine(fpSchema,
2593 : "<xs:element name=\"FeatureCollection\" "
2594 : "type=\"%s:FeatureCollectionType\" "
2595 : "substitutionGroup=\"gml:_FeatureCollection\"%s>",
2596 : pszPrefix, bHasUniqueConstraints ? "" : "/");
2597 : }
2598 :
2599 97 : if (bHasUniqueConstraints)
2600 : {
2601 0 : for (int iLayer = 0; iLayer < nLayerCount; iLayer++)
2602 : {
2603 0 : OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
2604 0 : const int nFieldCount = poFDefn->GetFieldCount();
2605 0 : for (int iField = 0; iField < nFieldCount; iField++)
2606 : {
2607 : const OGRFieldDefn *poFieldDefn =
2608 0 : poFDefn->GetFieldDefn(iField);
2609 0 : if (poFieldDefn->IsUnique())
2610 : {
2611 0 : PrintLine(
2612 : fpSchema,
2613 : " <xs:unique name=\"uniqueConstraint_%s_%s\">",
2614 0 : poFDefn->GetName(), poFieldDefn->GetNameRef());
2615 0 : PrintLine(fpSchema,
2616 : " <xs:selector "
2617 : "xpath=\"%s:featureMember/%s:%s\"/>",
2618 : pszFeatureMemberPrefix, pszPrefix,
2619 0 : poFDefn->GetName());
2620 0 : PrintLine(fpSchema, " <xs:field xpath=\"%s:%s\"/>",
2621 : pszPrefix, poFieldDefn->GetNameRef());
2622 0 : PrintLine(fpSchema, " </xs:unique>");
2623 : }
2624 : }
2625 : }
2626 0 : PrintLine(fpSchema, "</xs:element>");
2627 : }
2628 : }
2629 :
2630 : // Define the FeatureCollectionType
2631 98 : if (IsGML3Output() && !bGMLFeatureCollection)
2632 : {
2633 87 : PrintLine(fpSchema, "<xs:complexType name=\"FeatureCollectionType\">");
2634 87 : PrintLine(fpSchema, " <xs:complexContent>");
2635 87 : if (IsGML3DeegreeOutput())
2636 : {
2637 1 : PrintLine(fpSchema, " <xs:extension "
2638 : "base=\"gml:AbstractFeatureCollectionType\">");
2639 1 : PrintLine(fpSchema, " <xs:sequence>");
2640 1 : PrintLine(fpSchema, " <xs:element name=\"featureMember\" "
2641 : "minOccurs=\"0\" maxOccurs=\"unbounded\">");
2642 : }
2643 : else
2644 : {
2645 86 : PrintLine(fpSchema,
2646 : " <xs:extension base=\"gml:AbstractFeatureType\">");
2647 86 : PrintLine(
2648 : fpSchema,
2649 : " <xs:sequence minOccurs=\"0\" maxOccurs=\"unbounded\">");
2650 86 : PrintLine(fpSchema, " <xs:element name=\"featureMember\">");
2651 : }
2652 87 : PrintLine(fpSchema, " <xs:complexType>");
2653 87 : if (IsGML32Output())
2654 : {
2655 75 : PrintLine(fpSchema, " <xs:complexContent>");
2656 75 : PrintLine(fpSchema, " <xs:extension "
2657 : "base=\"gml:AbstractFeatureMemberType\">");
2658 75 : PrintLine(fpSchema, " <xs:sequence>");
2659 75 : PrintLine(
2660 : fpSchema,
2661 : " <xs:element ref=\"gml:AbstractFeature\"/>");
2662 75 : PrintLine(fpSchema, " </xs:sequence>");
2663 75 : PrintLine(fpSchema, " </xs:extension>");
2664 75 : PrintLine(fpSchema, " </xs:complexContent>");
2665 : }
2666 : else
2667 : {
2668 12 : PrintLine(fpSchema, " <xs:sequence>");
2669 12 : PrintLine(fpSchema,
2670 : " <xs:element ref=\"gml:_Feature\"/>");
2671 12 : PrintLine(fpSchema, " </xs:sequence>");
2672 : }
2673 87 : PrintLine(fpSchema, " </xs:complexType>");
2674 87 : PrintLine(fpSchema, " </xs:element>");
2675 87 : PrintLine(fpSchema, " </xs:sequence>");
2676 87 : PrintLine(fpSchema, " </xs:extension>");
2677 87 : PrintLine(fpSchema, " </xs:complexContent>");
2678 87 : PrintLine(fpSchema, "</xs:complexType>");
2679 : }
2680 11 : else if (!bGMLFeatureCollection)
2681 : {
2682 10 : PrintLine(fpSchema, "<xs:complexType name=\"FeatureCollectionType\">");
2683 10 : PrintLine(fpSchema, " <xs:complexContent>");
2684 10 : PrintLine(
2685 : fpSchema,
2686 : " <xs:extension base=\"gml:AbstractFeatureCollectionType\">");
2687 10 : PrintLine(fpSchema, " <xs:attribute name=\"lockId\" "
2688 : "type=\"xs:string\" use=\"optional\"/>");
2689 10 : PrintLine(fpSchema, " <xs:attribute name=\"scope\" "
2690 : "type=\"xs:string\" use=\"optional\"/>");
2691 10 : PrintLine(fpSchema, " </xs:extension>");
2692 10 : PrintLine(fpSchema, " </xs:complexContent>");
2693 10 : PrintLine(fpSchema, "</xs:complexType>");
2694 : }
2695 :
2696 : // Define the schema for each layer.
2697 223 : for (int iLayer = 0; iLayer < nLayerCount; iLayer++)
2698 : {
2699 125 : OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
2700 :
2701 : // Emit initial stuff for a feature type.
2702 125 : if (IsGML32Output())
2703 : {
2704 91 : PrintLine(fpSchema,
2705 : "<xs:element name=\"%s\" type=\"%s:%s_Type\" "
2706 : "substitutionGroup=\"gml:AbstractFeature\"/>",
2707 91 : poFDefn->GetName(), pszPrefix, poFDefn->GetName());
2708 : }
2709 : else
2710 : {
2711 34 : PrintLine(fpSchema,
2712 : "<xs:element name=\"%s\" type=\"%s:%s_Type\" "
2713 : "substitutionGroup=\"gml:_Feature\"/>",
2714 34 : poFDefn->GetName(), pszPrefix, poFDefn->GetName());
2715 : }
2716 :
2717 125 : PrintLine(fpSchema, "<xs:complexType name=\"%s_Type\">",
2718 125 : poFDefn->GetName());
2719 125 : PrintLine(fpSchema, " <xs:complexContent>");
2720 125 : PrintLine(fpSchema,
2721 : " <xs:extension base=\"gml:AbstractFeatureType\">");
2722 125 : PrintLine(fpSchema, " <xs:sequence>");
2723 :
2724 248 : for (int iGeomField = 0; iGeomField < poFDefn->GetGeomFieldCount();
2725 : iGeomField++)
2726 : {
2727 : OGRGeomFieldDefn *poFieldDefn =
2728 123 : poFDefn->GetGeomFieldDefn(iGeomField);
2729 :
2730 : // Define the geometry attribute.
2731 123 : const char *pszGeometryTypeName = "GeometryPropertyType";
2732 123 : const char *pszGeomTypeComment = "";
2733 123 : OGRwkbGeometryType eGType = wkbFlatten(poFieldDefn->GetType());
2734 123 : switch (eGType)
2735 : {
2736 25 : case wkbPoint:
2737 25 : pszGeometryTypeName = "PointPropertyType";
2738 25 : break;
2739 12 : case wkbLineString:
2740 : case wkbCircularString:
2741 : case wkbCompoundCurve:
2742 12 : if (IsGML3Output())
2743 : {
2744 12 : if (eGType == wkbLineString)
2745 11 : pszGeomTypeComment =
2746 : " <!-- restricted to LineString -->";
2747 1 : else if (eGType == wkbCircularString)
2748 0 : pszGeomTypeComment =
2749 : " <!-- contains CircularString -->";
2750 1 : else if (eGType == wkbCompoundCurve)
2751 1 : pszGeomTypeComment =
2752 : " <!-- contains CompoundCurve -->";
2753 12 : pszGeometryTypeName = "CurvePropertyType";
2754 : }
2755 : else
2756 0 : pszGeometryTypeName = "LineStringPropertyType";
2757 12 : break;
2758 16 : case wkbPolygon:
2759 : case wkbCurvePolygon:
2760 16 : if (IsGML3Output())
2761 : {
2762 14 : if (eGType == wkbPolygon)
2763 13 : pszGeomTypeComment =
2764 : " <!-- restricted to Polygon -->";
2765 1 : else if (eGType == wkbCurvePolygon)
2766 1 : pszGeomTypeComment =
2767 : " <!-- contains CurvePolygon -->";
2768 14 : pszGeometryTypeName = "SurfacePropertyType";
2769 : }
2770 : else
2771 2 : pszGeometryTypeName = "PolygonPropertyType";
2772 16 : break;
2773 6 : case wkbMultiPoint:
2774 6 : pszGeometryTypeName = "MultiPointPropertyType";
2775 6 : break;
2776 9 : case wkbMultiLineString:
2777 : case wkbMultiCurve:
2778 9 : if (IsGML3Output())
2779 : {
2780 9 : if (eGType == wkbMultiLineString)
2781 8 : pszGeomTypeComment =
2782 : " <!-- restricted to MultiLineString -->";
2783 1 : else if (eGType == wkbMultiCurve)
2784 1 : pszGeomTypeComment =
2785 : " <!-- contains non-linear MultiCurve -->";
2786 9 : pszGeometryTypeName = "MultiCurvePropertyType";
2787 : }
2788 : else
2789 0 : pszGeometryTypeName = "MultiLineStringPropertyType";
2790 9 : break;
2791 12 : case wkbMultiPolygon:
2792 : case wkbMultiSurface:
2793 12 : if (IsGML3Output())
2794 : {
2795 11 : if (eGType == wkbMultiPolygon)
2796 10 : pszGeomTypeComment =
2797 : " <!-- restricted to MultiPolygon -->";
2798 1 : else if (eGType == wkbMultiSurface)
2799 1 : pszGeomTypeComment =
2800 : " <!-- contains non-linear MultiSurface -->";
2801 11 : pszGeometryTypeName = "MultiSurfacePropertyType";
2802 : }
2803 : else
2804 1 : pszGeometryTypeName = "MultiPolygonPropertyType";
2805 12 : break;
2806 6 : case wkbGeometryCollection:
2807 6 : pszGeometryTypeName = "MultiGeometryPropertyType";
2808 6 : break;
2809 37 : default:
2810 37 : break;
2811 : }
2812 :
2813 123 : const auto poSRS = poFieldDefn->GetSpatialRef();
2814 246 : std::string osSRSNameComment;
2815 123 : if (poSRS)
2816 : {
2817 30 : bool bCoordSwap = false;
2818 : char *pszSRSName =
2819 30 : GML_GetSRSName(poSRS, GetSRSNameFormat(), &bCoordSwap);
2820 30 : if (pszSRSName[0])
2821 : {
2822 30 : osSRSNameComment = "<!--";
2823 30 : osSRSNameComment += pszSRSName;
2824 30 : osSRSNameComment += " -->";
2825 : }
2826 30 : CPLFree(pszSRSName);
2827 : }
2828 :
2829 123 : int nMinOccurs = poFieldDefn->IsNullable() ? 0 : 1;
2830 123 : const auto &oCoordPrec = poFieldDefn->GetCoordinatePrecision();
2831 123 : if (oCoordPrec.dfXYResolution ==
2832 122 : OGRGeomCoordinatePrecision::UNKNOWN &&
2833 122 : oCoordPrec.dfZResolution == OGRGeomCoordinatePrecision::UNKNOWN)
2834 : {
2835 122 : PrintLine(
2836 : fpSchema,
2837 : " <xs:element name=\"%s\" type=\"gml:%s\" "
2838 : "nillable=\"true\" minOccurs=\"%d\" maxOccurs=\"1\"/>%s%s",
2839 : poFieldDefn->GetNameRef(), pszGeometryTypeName, nMinOccurs,
2840 : pszGeomTypeComment, osSRSNameComment.c_str());
2841 : }
2842 : else
2843 : {
2844 1 : PrintLine(fpSchema,
2845 : " <xs:element name=\"%s\" type=\"gml:%s\" "
2846 : "nillable=\"true\" minOccurs=\"%d\" maxOccurs=\"1\">",
2847 : poFieldDefn->GetNameRef(), pszGeometryTypeName,
2848 : nMinOccurs);
2849 1 : PrintLine(fpSchema, " <xs:annotation>");
2850 1 : PrintLine(fpSchema, " <xs:appinfo "
2851 : "source=\"http://ogr.maptools.org/\">");
2852 1 : if (oCoordPrec.dfXYResolution !=
2853 : OGRGeomCoordinatePrecision::UNKNOWN)
2854 : {
2855 1 : PrintLine(fpSchema,
2856 : " "
2857 : "<ogr:xy_coordinate_resolution>%g</"
2858 : "ogr:xy_coordinate_resolution>",
2859 1 : oCoordPrec.dfXYResolution);
2860 : }
2861 1 : if (oCoordPrec.dfZResolution !=
2862 : OGRGeomCoordinatePrecision::UNKNOWN)
2863 : {
2864 1 : PrintLine(fpSchema,
2865 : " "
2866 : "<ogr:z_coordinate_resolution>%g</"
2867 : "ogr:z_coordinate_resolution>",
2868 1 : oCoordPrec.dfZResolution);
2869 : }
2870 1 : PrintLine(fpSchema, " </xs:appinfo>");
2871 1 : PrintLine(fpSchema, " </xs:annotation>");
2872 1 : PrintLine(fpSchema, " </xs:element>%s%s",
2873 : pszGeomTypeComment, osSRSNameComment.c_str());
2874 : }
2875 : }
2876 :
2877 : // Emit each of the attributes.
2878 306 : for (int iField = 0; iField < poFDefn->GetFieldCount(); iField++)
2879 : {
2880 181 : OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
2881 :
2882 351 : if (IsGML3Output() &&
2883 170 : strcmp(poFieldDefn->GetNameRef(), "gml_id") == 0)
2884 1 : continue;
2885 191 : else if (!IsGML3Output() &&
2886 11 : strcmp(poFieldDefn->GetNameRef(), "fid") == 0)
2887 0 : continue;
2888 :
2889 202 : const auto AddComment = [this, fpSchema, poFieldDefn]()
2890 : {
2891 180 : if (!poFieldDefn->GetComment().empty())
2892 : {
2893 11 : char *pszComment = CPLEscapeString(
2894 11 : poFieldDefn->GetComment().c_str(), -1, CPLES_XML);
2895 11 : PrintLine(fpSchema,
2896 : " "
2897 : "<xs:annotation><xs:documentation>%s</"
2898 : "xs:documentation></xs:annotation>",
2899 : pszComment);
2900 11 : CPLFree(pszComment);
2901 : }
2902 360 : };
2903 :
2904 180 : int nMinOccurs = poFieldDefn->IsNullable() ? 0 : 1;
2905 180 : const OGRFieldType eType = poFieldDefn->GetType();
2906 180 : if (eType == OFTInteger || eType == OFTIntegerList)
2907 : {
2908 : int nWidth =
2909 32 : poFieldDefn->GetWidth() > 0 ? poFieldDefn->GetWidth() : 16;
2910 :
2911 32 : PrintLine(fpSchema,
2912 : " <xs:element name=\"%s\" nillable=\"true\" "
2913 : "minOccurs=\"%d\" maxOccurs=\"%s\">",
2914 : poFieldDefn->GetNameRef(), nMinOccurs,
2915 : eType == OFTIntegerList ? "unbounded" : "1");
2916 32 : AddComment();
2917 32 : PrintLine(fpSchema, " <xs:simpleType>");
2918 32 : if (poFieldDefn->GetSubType() == OFSTBoolean)
2919 : {
2920 3 : PrintLine(
2921 : fpSchema,
2922 : " <xs:restriction base=\"xs:boolean\">");
2923 : }
2924 29 : else if (poFieldDefn->GetSubType() == OFSTInt16)
2925 : {
2926 1 : PrintLine(fpSchema,
2927 : " <xs:restriction base=\"xs:short\">");
2928 : }
2929 : else
2930 : {
2931 28 : PrintLine(
2932 : fpSchema,
2933 : " <xs:restriction base=\"xs:integer\">");
2934 28 : PrintLine(fpSchema,
2935 : " <xs:totalDigits value=\"%d\"/>",
2936 : nWidth);
2937 : }
2938 32 : PrintLine(fpSchema, " </xs:restriction>");
2939 32 : PrintLine(fpSchema, " </xs:simpleType>");
2940 32 : PrintLine(fpSchema, " </xs:element>");
2941 : }
2942 148 : else if (eType == OFTInteger64 || eType == OFTInteger64List)
2943 : {
2944 : int nWidth =
2945 17 : poFieldDefn->GetWidth() > 0 ? poFieldDefn->GetWidth() : 16;
2946 :
2947 17 : PrintLine(fpSchema,
2948 : " <xs:element name=\"%s\" nillable=\"true\" "
2949 : "minOccurs=\"%d\" maxOccurs=\"%s\">",
2950 : poFieldDefn->GetNameRef(), nMinOccurs,
2951 : eType == OFTInteger64List ? "unbounded" : "1");
2952 17 : AddComment();
2953 17 : PrintLine(fpSchema, " <xs:simpleType>");
2954 17 : if (poFieldDefn->GetSubType() == OFSTBoolean)
2955 : {
2956 0 : PrintLine(
2957 : fpSchema,
2958 : " <xs:restriction base=\"xs:boolean\">");
2959 : }
2960 17 : else if (poFieldDefn->GetSubType() == OFSTInt16)
2961 : {
2962 0 : PrintLine(fpSchema,
2963 : " <xs:restriction base=\"xs:short\">");
2964 : }
2965 : else
2966 : {
2967 17 : PrintLine(fpSchema,
2968 : " <xs:restriction base=\"xs:long\">");
2969 17 : PrintLine(fpSchema,
2970 : " <xs:totalDigits value=\"%d\"/>",
2971 : nWidth);
2972 : }
2973 17 : PrintLine(fpSchema, " </xs:restriction>");
2974 17 : PrintLine(fpSchema, " </xs:simpleType>");
2975 17 : PrintLine(fpSchema, " </xs:element>");
2976 : }
2977 131 : else if (eType == OFTReal || eType == OFTRealList)
2978 : {
2979 : int nWidth, nDecimals;
2980 :
2981 35 : nWidth = poFieldDefn->GetWidth();
2982 35 : nDecimals = poFieldDefn->GetPrecision();
2983 :
2984 35 : PrintLine(fpSchema,
2985 : " <xs:element name=\"%s\" nillable=\"true\" "
2986 : "minOccurs=\"%d\" maxOccurs=\"%s\">",
2987 : poFieldDefn->GetNameRef(), nMinOccurs,
2988 : eType == OFTRealList ? "unbounded" : "1");
2989 35 : AddComment();
2990 35 : PrintLine(fpSchema, " <xs:simpleType>");
2991 35 : if (poFieldDefn->GetSubType() == OFSTFloat32)
2992 1 : PrintLine(fpSchema,
2993 : " <xs:restriction base=\"xs:float\">");
2994 : else
2995 34 : PrintLine(
2996 : fpSchema,
2997 : " <xs:restriction base=\"xs:decimal\">");
2998 35 : if (nWidth > 0)
2999 : {
3000 14 : PrintLine(fpSchema,
3001 : " <xs:totalDigits value=\"%d\"/>",
3002 : nWidth);
3003 14 : PrintLine(fpSchema,
3004 : " <xs:fractionDigits value=\"%d\"/>",
3005 : nDecimals);
3006 : }
3007 35 : PrintLine(fpSchema, " </xs:restriction>");
3008 35 : PrintLine(fpSchema, " </xs:simpleType>");
3009 35 : PrintLine(fpSchema, " </xs:element>");
3010 : }
3011 96 : else if (eType == OFTString || eType == OFTStringList)
3012 : {
3013 58 : PrintLine(fpSchema,
3014 : " <xs:element name=\"%s\" nillable=\"true\" "
3015 : "minOccurs=\"%d\" maxOccurs=\"%s\">",
3016 : poFieldDefn->GetNameRef(), nMinOccurs,
3017 : eType == OFTStringList ? "unbounded" : "1");
3018 58 : AddComment();
3019 58 : PrintLine(fpSchema, " <xs:simpleType>");
3020 58 : PrintLine(fpSchema,
3021 : " <xs:restriction base=\"xs:string\">");
3022 58 : if (poFieldDefn->GetWidth() != 0)
3023 : {
3024 10 : PrintLine(fpSchema,
3025 : " <xs:maxLength value=\"%d\"/>",
3026 : poFieldDefn->GetWidth());
3027 : }
3028 58 : PrintLine(fpSchema, " </xs:restriction>");
3029 58 : PrintLine(fpSchema, " </xs:simpleType>");
3030 58 : PrintLine(fpSchema, " </xs:element>");
3031 : }
3032 38 : else if (eType == OFTDate)
3033 : {
3034 18 : PrintLine(fpSchema,
3035 : " <xs:element name=\"%s\" nillable=\"true\" "
3036 : "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:date\">",
3037 : poFieldDefn->GetNameRef(), nMinOccurs);
3038 18 : AddComment();
3039 18 : PrintLine(fpSchema, " </xs:element>");
3040 : }
3041 20 : else if (eType == OFTTime)
3042 : {
3043 2 : PrintLine(fpSchema,
3044 : " <xs:element name=\"%s\" nillable=\"true\" "
3045 : "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:time\">",
3046 : poFieldDefn->GetNameRef(), nMinOccurs);
3047 2 : AddComment();
3048 2 : PrintLine(fpSchema, " </xs:element>");
3049 : }
3050 18 : else if (eType == OFTDateTime)
3051 : {
3052 18 : PrintLine(
3053 : fpSchema,
3054 : " <xs:element name=\"%s\" nillable=\"true\" "
3055 : "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:dateTime\">",
3056 : poFieldDefn->GetNameRef(), nMinOccurs);
3057 18 : AddComment();
3058 18 : PrintLine(fpSchema, " </xs:element>");
3059 : }
3060 : else
3061 : {
3062 : // TODO.
3063 : }
3064 : } // Next field.
3065 :
3066 : // Finish off feature type.
3067 125 : PrintLine(fpSchema, " </xs:sequence>");
3068 125 : PrintLine(fpSchema, " </xs:extension>");
3069 125 : PrintLine(fpSchema, " </xs:complexContent>");
3070 125 : PrintLine(fpSchema, "</xs:complexType>");
3071 : } // Next layer.
3072 :
3073 98 : PrintLine(fpSchema, "</xs:schema>");
3074 :
3075 : // Move schema to the start of the file.
3076 98 : if (fpSchema == fpOutput)
3077 : {
3078 : // Read the schema into memory.
3079 0 : int nSchemaSize = static_cast<int>(VSIFTellL(fpSchema) - nSchemaStart);
3080 0 : char *pszSchema = static_cast<char *>(CPLMalloc(nSchemaSize + 1));
3081 :
3082 0 : VSIFSeekL(fpSchema, nSchemaStart, SEEK_SET);
3083 :
3084 0 : VSIFReadL(pszSchema, 1, nSchemaSize, fpSchema);
3085 0 : pszSchema[nSchemaSize] = '\0';
3086 :
3087 : // Move file data down by "schema size" bytes from after <?xml> header
3088 : // so we have room insert the schema. Move in pretty big chunks.
3089 0 : int nChunkSize = std::min(nSchemaStart - nSchemaInsertLocation, 250000);
3090 0 : char *pszChunk = static_cast<char *>(CPLMalloc(nChunkSize));
3091 :
3092 0 : for (int nEndOfUnmovedData = nSchemaStart;
3093 0 : nEndOfUnmovedData > nSchemaInsertLocation;)
3094 : {
3095 : const int nBytesToMove =
3096 0 : std::min(nChunkSize, nEndOfUnmovedData - nSchemaInsertLocation);
3097 :
3098 0 : VSIFSeekL(fpSchema, nEndOfUnmovedData - nBytesToMove, SEEK_SET);
3099 0 : VSIFReadL(pszChunk, 1, nBytesToMove, fpSchema);
3100 0 : VSIFSeekL(fpSchema, nEndOfUnmovedData - nBytesToMove + nSchemaSize,
3101 : SEEK_SET);
3102 0 : VSIFWriteL(pszChunk, 1, nBytesToMove, fpSchema);
3103 :
3104 0 : nEndOfUnmovedData -= nBytesToMove;
3105 : }
3106 :
3107 0 : CPLFree(pszChunk);
3108 :
3109 : // Write the schema in the opened slot.
3110 0 : VSIFSeekL(fpSchema, nSchemaInsertLocation, SEEK_SET);
3111 0 : VSIFWriteL(pszSchema, 1, nSchemaSize, fpSchema);
3112 :
3113 0 : VSIFSeekL(fpSchema, 0, SEEK_END);
3114 :
3115 0 : nBoundedByLocation += nSchemaSize;
3116 :
3117 0 : CPLFree(pszSchema);
3118 : }
3119 : else
3120 : {
3121 : // Close external schema files.
3122 98 : VSIFCloseL(fpSchema);
3123 : }
3124 : }
3125 :
3126 : /************************************************************************/
3127 : /* PrintLine() */
3128 : /************************************************************************/
3129 :
3130 8564 : void OGRGMLDataSource::PrintLine(VSILFILE *fp, const char *fmt, ...)
3131 : {
3132 17128 : CPLString osWork;
3133 : va_list args;
3134 :
3135 8564 : va_start(args, fmt);
3136 8564 : osWork.vPrintf(fmt, args);
3137 8564 : va_end(args);
3138 :
3139 : #ifdef _WIN32
3140 : constexpr const char *pszEOL = "\r\n";
3141 : #else
3142 8564 : constexpr const char *pszEOL = "\n";
3143 : #endif
3144 :
3145 17123 : if (VSIFWriteL(osWork.data(), osWork.size(), 1, fp) != 1 ||
3146 8559 : VSIFWriteL(pszEOL, strlen(pszEOL), 1, fp) != 1)
3147 : {
3148 5 : m_bWriteError = true;
3149 5 : ReportError(CE_Failure, CPLE_FileIO, "Could not write line %s",
3150 : osWork.c_str());
3151 : }
3152 8564 : }
3153 :
3154 : /************************************************************************/
3155 : /* OGRGMLSingleFeatureLayer */
3156 : /************************************************************************/
3157 :
3158 : class OGRGMLSingleFeatureLayer final : public OGRLayer
3159 : {
3160 : private:
3161 : const int nVal;
3162 : OGRFeatureDefn *poFeatureDefn = nullptr;
3163 : int iNextShapeId = 0;
3164 :
3165 : CPL_DISALLOW_COPY_ASSIGN(OGRGMLSingleFeatureLayer)
3166 :
3167 : public:
3168 : explicit OGRGMLSingleFeatureLayer(int nVal);
3169 :
3170 0 : virtual ~OGRGMLSingleFeatureLayer()
3171 0 : {
3172 0 : poFeatureDefn->Release();
3173 0 : }
3174 :
3175 0 : virtual void ResetReading() override
3176 : {
3177 0 : iNextShapeId = 0;
3178 0 : }
3179 :
3180 : virtual OGRFeature *GetNextFeature() override;
3181 :
3182 0 : virtual OGRFeatureDefn *GetLayerDefn() override
3183 : {
3184 0 : return poFeatureDefn;
3185 : }
3186 :
3187 0 : virtual int TestCapability(const char *) override
3188 : {
3189 0 : return FALSE;
3190 : }
3191 : };
3192 :
3193 : /************************************************************************/
3194 : /* OGRGMLSingleFeatureLayer() */
3195 : /************************************************************************/
3196 :
3197 0 : OGRGMLSingleFeatureLayer::OGRGMLSingleFeatureLayer(int nValIn)
3198 0 : : nVal(nValIn), poFeatureDefn(new OGRFeatureDefn("SELECT")), iNextShapeId(0)
3199 : {
3200 0 : poFeatureDefn->Reference();
3201 0 : OGRFieldDefn oField("Validates", OFTInteger);
3202 0 : poFeatureDefn->AddFieldDefn(&oField);
3203 0 : }
3204 :
3205 : /************************************************************************/
3206 : /* GetNextFeature() */
3207 : /************************************************************************/
3208 :
3209 0 : OGRFeature *OGRGMLSingleFeatureLayer::GetNextFeature()
3210 : {
3211 0 : if (iNextShapeId != 0)
3212 0 : return nullptr;
3213 :
3214 0 : OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
3215 0 : poFeature->SetField(0, nVal);
3216 0 : poFeature->SetFID(iNextShapeId++);
3217 0 : return poFeature;
3218 : }
3219 :
3220 : /************************************************************************/
3221 : /* ExecuteSQL() */
3222 : /************************************************************************/
3223 :
3224 6 : OGRLayer *OGRGMLDataSource::ExecuteSQL(const char *pszSQLCommand,
3225 : OGRGeometry *poSpatialFilter,
3226 : const char *pszDialect)
3227 : {
3228 6 : if (poReader != nullptr && EQUAL(pszSQLCommand, "SELECT ValidateSchema()"))
3229 : {
3230 0 : bool bIsValid = false;
3231 0 : if (!osXSDFilename.empty())
3232 : {
3233 0 : CPLErrorReset();
3234 : bIsValid =
3235 0 : CPL_TO_BOOL(CPLValidateXML(osFilename, osXSDFilename, nullptr));
3236 : }
3237 0 : return new OGRGMLSingleFeatureLayer(bIsValid);
3238 : }
3239 :
3240 6 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, pszDialect);
3241 : }
3242 :
3243 : /************************************************************************/
3244 : /* ReleaseResultSet() */
3245 : /************************************************************************/
3246 :
3247 5 : void OGRGMLDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
3248 : {
3249 5 : delete poResultsSet;
3250 5 : }
3251 :
3252 : /************************************************************************/
3253 : /* FindAndParseTopElements() */
3254 : /************************************************************************/
3255 :
3256 431 : void OGRGMLDataSource::FindAndParseTopElements(VSILFILE *fp)
3257 : {
3258 : // Build a shortened XML file that contain only the global
3259 : // boundedBy element, so as to be able to parse it easily.
3260 :
3261 : char szStartTag[128];
3262 431 : char *pszXML = static_cast<char *>(CPLMalloc(8192 + 128 + 3 + 1));
3263 431 : VSIFSeekL(fp, 0, SEEK_SET);
3264 431 : int nRead = static_cast<int>(VSIFReadL(pszXML, 1, 8192, fp));
3265 431 : pszXML[nRead] = 0;
3266 :
3267 431 : const char *pszStartTag = strchr(pszXML, '<');
3268 431 : if (pszStartTag != nullptr)
3269 : {
3270 784 : while (pszStartTag != nullptr && pszStartTag[1] == '?')
3271 353 : pszStartTag = strchr(pszStartTag + 1, '<');
3272 :
3273 431 : if (pszStartTag != nullptr)
3274 : {
3275 431 : pszStartTag++;
3276 431 : const char *pszEndTag = nullptr;
3277 9221 : for (const char *pszIter = pszStartTag; *pszIter != '\0'; pszIter++)
3278 : {
3279 9221 : if (isspace(static_cast<unsigned char>(*pszIter)) ||
3280 8790 : *pszIter == '>')
3281 : {
3282 431 : pszEndTag = pszIter;
3283 431 : break;
3284 : }
3285 : }
3286 431 : if (pszEndTag != nullptr && pszEndTag - pszStartTag < 128)
3287 : {
3288 431 : memcpy(szStartTag, pszStartTag, pszEndTag - pszStartTag);
3289 431 : szStartTag[pszEndTag - pszStartTag] = '\0';
3290 : }
3291 : else
3292 0 : pszStartTag = nullptr;
3293 : }
3294 : }
3295 :
3296 431 : const char *pszFeatureMember = strstr(pszXML, "<gml:featureMember");
3297 431 : if (pszFeatureMember == nullptr)
3298 270 : pszFeatureMember = strstr(pszXML, ":featureMember>");
3299 431 : if (pszFeatureMember == nullptr)
3300 161 : pszFeatureMember = strstr(pszXML, "<wfs:member>");
3301 :
3302 : // Is it a standalone geometry ?
3303 431 : if (pszFeatureMember == nullptr && pszStartTag != nullptr)
3304 : {
3305 100 : const char *pszElement = szStartTag;
3306 100 : const char *pszColon = strchr(pszElement, ':');
3307 100 : if (pszColon)
3308 73 : pszElement = pszColon + 1;
3309 100 : if (OGRGMLIsGeometryElement(pszElement))
3310 : {
3311 1 : VSIFSeekL(fp, 0, SEEK_END);
3312 1 : const auto nLen = VSIFTellL(fp);
3313 1 : if (nLen < 10 * 1024 * 1024U)
3314 : {
3315 1 : VSIFSeekL(fp, 0, SEEK_SET);
3316 2 : std::string osBuffer;
3317 : try
3318 : {
3319 1 : osBuffer.resize(static_cast<size_t>(nLen));
3320 1 : VSIFReadL(&osBuffer[0], 1, osBuffer.size(), fp);
3321 : }
3322 0 : catch (const std::exception &)
3323 : {
3324 : }
3325 1 : CPLPushErrorHandler(CPLQuietErrorHandler);
3326 1 : CPLXMLNode *psTree = CPLParseXMLString(osBuffer.data());
3327 1 : CPLPopErrorHandler();
3328 1 : CPLErrorReset();
3329 1 : if (psTree)
3330 : {
3331 1 : m_poStandaloneGeom.reset(GML2OGRGeometry_XMLNode(
3332 : psTree, false, 0, 0, false, true, false));
3333 :
3334 1 : if (m_poStandaloneGeom)
3335 : {
3336 2 : for (CPLXMLNode *psCur = psTree; psCur;
3337 1 : psCur = psCur->psNext)
3338 : {
3339 2 : if (psCur->eType == CXT_Element &&
3340 2 : strcmp(psCur->pszValue, szStartTag) == 0)
3341 : {
3342 : const char *pszSRSName =
3343 1 : CPLGetXMLValue(psCur, "srsName", nullptr);
3344 1 : if (pszSRSName)
3345 : {
3346 1 : m_oStandaloneGeomSRS.SetFromUserInput(
3347 : pszSRSName,
3348 : OGRSpatialReference::
3349 : SET_FROM_USER_INPUT_LIMITATIONS_get());
3350 1 : m_oStandaloneGeomSRS.SetAxisMappingStrategy(
3351 : OAMS_TRADITIONAL_GIS_ORDER);
3352 1 : if (GML_IsSRSLatLongOrder(pszSRSName))
3353 1 : m_poStandaloneGeom->swapXY();
3354 : }
3355 1 : break;
3356 : }
3357 : }
3358 : }
3359 1 : CPLDestroyXMLNode(psTree);
3360 : }
3361 : }
3362 : }
3363 : }
3364 :
3365 431 : const char *pszDescription = strstr(pszXML, "<gml:description>");
3366 431 : if (pszDescription &&
3367 1 : (pszFeatureMember == nullptr || pszDescription < pszFeatureMember))
3368 : {
3369 8 : pszDescription += strlen("<gml:description>");
3370 : const char *pszEndDescription =
3371 8 : strstr(pszDescription, "</gml:description>");
3372 8 : if (pszEndDescription)
3373 : {
3374 16 : CPLString osTmp(pszDescription);
3375 8 : osTmp.resize(pszEndDescription - pszDescription);
3376 8 : char *pszTmp = CPLUnescapeString(osTmp, nullptr, CPLES_XML);
3377 8 : if (pszTmp)
3378 8 : SetMetadataItem("DESCRIPTION", pszTmp);
3379 8 : CPLFree(pszTmp);
3380 : }
3381 : }
3382 :
3383 431 : const char *l_pszName = strstr(pszXML, "<gml:name");
3384 431 : if (l_pszName)
3385 11 : l_pszName = strchr(l_pszName, '>');
3386 431 : if (l_pszName &&
3387 4 : (pszFeatureMember == nullptr || l_pszName < pszFeatureMember))
3388 : {
3389 8 : l_pszName++;
3390 8 : const char *pszEndName = strstr(l_pszName, "</gml:name>");
3391 8 : if (pszEndName)
3392 : {
3393 16 : CPLString osTmp(l_pszName);
3394 8 : osTmp.resize(pszEndName - l_pszName);
3395 8 : char *pszTmp = CPLUnescapeString(osTmp, nullptr, CPLES_XML);
3396 8 : if (pszTmp)
3397 8 : SetMetadataItem("NAME", pszTmp);
3398 8 : CPLFree(pszTmp);
3399 : }
3400 : }
3401 :
3402 : // Detect a few fields in gml: namespace inside features
3403 431 : if (pszFeatureMember)
3404 : {
3405 331 : if (strstr(pszFeatureMember, "<gml:description>"))
3406 1 : m_aosGMLExtraElements.push_back("description");
3407 331 : if (strstr(pszFeatureMember, "<gml:identifier>") ||
3408 331 : strstr(pszFeatureMember, "<gml:identifier "))
3409 1 : m_aosGMLExtraElements.push_back("identifier");
3410 331 : if (strstr(pszFeatureMember, "<gml:name>") ||
3411 327 : strstr(pszFeatureMember, "<gml:name "))
3412 4 : m_aosGMLExtraElements.push_back("name");
3413 : }
3414 :
3415 431 : char *pszEndBoundedBy = strstr(pszXML, "</wfs:boundedBy>");
3416 431 : bool bWFSBoundedBy = false;
3417 431 : if (pszEndBoundedBy != nullptr)
3418 2 : bWFSBoundedBy = true;
3419 : else
3420 429 : pszEndBoundedBy = strstr(pszXML, "</gml:boundedBy>");
3421 431 : if (pszStartTag != nullptr && pszEndBoundedBy != nullptr)
3422 : {
3423 213 : char szSRSName[128] = {};
3424 :
3425 : // Find a srsName somewhere for some WFS 2.0 documents that have not it
3426 : // set at the <wfs:boundedBy> element. e.g.
3427 : // http://geoserv.weichand.de:8080/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=bvv:gmd_ex
3428 213 : if (bIsWFS)
3429 : {
3430 24 : ExtractSRSName(pszXML, szSRSName, sizeof(szSRSName));
3431 : }
3432 :
3433 213 : pszEndBoundedBy[strlen("</gml:boundedBy>")] = '\0';
3434 213 : strcat(pszXML, "</");
3435 213 : strcat(pszXML, szStartTag);
3436 213 : strcat(pszXML, ">");
3437 :
3438 213 : CPLPushErrorHandler(CPLQuietErrorHandler);
3439 213 : CPLXMLNode *psXML = CPLParseXMLString(pszXML);
3440 213 : CPLPopErrorHandler();
3441 213 : CPLErrorReset();
3442 213 : if (psXML != nullptr)
3443 : {
3444 210 : CPLXMLNode *psBoundedBy = nullptr;
3445 210 : CPLXMLNode *psIter = psXML;
3446 416 : while (psIter != nullptr)
3447 : {
3448 416 : psBoundedBy = CPLGetXMLNode(
3449 : psIter, bWFSBoundedBy ? "wfs:boundedBy" : "gml:boundedBy");
3450 416 : if (psBoundedBy != nullptr)
3451 210 : break;
3452 206 : psIter = psIter->psNext;
3453 : }
3454 :
3455 210 : const char *pszLowerCorner = nullptr;
3456 210 : const char *pszUpperCorner = nullptr;
3457 210 : const char *pszSRSName = nullptr;
3458 210 : if (psBoundedBy != nullptr)
3459 : {
3460 : CPLXMLNode *psEnvelope =
3461 210 : CPLGetXMLNode(psBoundedBy, "gml:Envelope");
3462 210 : if (psEnvelope)
3463 : {
3464 108 : pszSRSName = CPLGetXMLValue(psEnvelope, "srsName", nullptr);
3465 : pszLowerCorner =
3466 108 : CPLGetXMLValue(psEnvelope, "gml:lowerCorner", nullptr);
3467 : pszUpperCorner =
3468 108 : CPLGetXMLValue(psEnvelope, "gml:upperCorner", nullptr);
3469 : }
3470 : }
3471 :
3472 210 : if (bIsWFS && pszSRSName == nullptr && pszLowerCorner != nullptr &&
3473 0 : pszUpperCorner != nullptr && szSRSName[0] != '\0')
3474 : {
3475 0 : pszSRSName = szSRSName;
3476 : }
3477 :
3478 210 : if (pszSRSName != nullptr && pszLowerCorner != nullptr &&
3479 : pszUpperCorner != nullptr)
3480 : {
3481 56 : char **papszLC = CSLTokenizeString(pszLowerCorner);
3482 56 : char **papszUC = CSLTokenizeString(pszUpperCorner);
3483 56 : if (CSLCount(papszLC) >= 2 && CSLCount(papszUC) >= 2)
3484 : {
3485 56 : CPLDebug("GML", "Global SRS = %s", pszSRSName);
3486 :
3487 56 : if (STARTS_WITH(pszSRSName,
3488 : "http://www.opengis.net/gml/srs/epsg.xml#"))
3489 : {
3490 0 : std::string osWork;
3491 0 : osWork.assign("EPSG:", 5);
3492 0 : osWork.append(pszSRSName + 40);
3493 0 : poReader->SetGlobalSRSName(osWork.c_str());
3494 : }
3495 : else
3496 : {
3497 56 : poReader->SetGlobalSRSName(pszSRSName);
3498 : }
3499 :
3500 56 : const double dfMinX = CPLAtofM(papszLC[0]);
3501 56 : const double dfMinY = CPLAtofM(papszLC[1]);
3502 56 : const double dfMaxX = CPLAtofM(papszUC[0]);
3503 56 : const double dfMaxY = CPLAtofM(papszUC[1]);
3504 :
3505 56 : SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
3506 : }
3507 56 : CSLDestroy(papszLC);
3508 56 : CSLDestroy(papszUC);
3509 : }
3510 :
3511 210 : CPLDestroyXMLNode(psXML);
3512 : }
3513 : }
3514 :
3515 431 : CPLFree(pszXML);
3516 431 : }
3517 :
3518 : /************************************************************************/
3519 : /* SetExtents() */
3520 : /************************************************************************/
3521 :
3522 56 : void OGRGMLDataSource::SetExtents(double dfMinX, double dfMinY, double dfMaxX,
3523 : double dfMaxY)
3524 : {
3525 56 : sBoundingRect.MinX = dfMinX;
3526 56 : sBoundingRect.MinY = dfMinY;
3527 56 : sBoundingRect.MaxX = dfMaxX;
3528 56 : sBoundingRect.MaxY = dfMaxY;
3529 56 : }
3530 :
3531 : /************************************************************************/
3532 : /* GetAppPrefix() */
3533 : /************************************************************************/
3534 :
3535 1100 : const char *OGRGMLDataSource::GetAppPrefix() const
3536 : {
3537 1100 : return CSLFetchNameValueDef(papszCreateOptions, "PREFIX", "ogr");
3538 : }
3539 :
3540 : /************************************************************************/
3541 : /* RemoveAppPrefix() */
3542 : /************************************************************************/
3543 :
3544 563 : bool OGRGMLDataSource::RemoveAppPrefix() const
3545 : {
3546 563 : if (CPLTestBool(
3547 563 : CSLFetchNameValueDef(papszCreateOptions, "STRIP_PREFIX", "FALSE")))
3548 26 : return true;
3549 537 : const char *pszPrefix = GetAppPrefix();
3550 537 : return pszPrefix[0] == '\0';
3551 : }
3552 :
3553 : /************************************************************************/
3554 : /* WriteFeatureBoundedBy() */
3555 : /************************************************************************/
3556 :
3557 181 : bool OGRGMLDataSource::WriteFeatureBoundedBy() const
3558 : {
3559 181 : return CPLTestBool(CSLFetchNameValueDef(
3560 362 : papszCreateOptions, "WRITE_FEATURE_BOUNDED_BY", "TRUE"));
3561 : }
3562 :
3563 : /************************************************************************/
3564 : /* GetSRSDimensionLoc() */
3565 : /************************************************************************/
3566 :
3567 215 : const char *OGRGMLDataSource::GetSRSDimensionLoc() const
3568 : {
3569 215 : return CSLFetchNameValue(papszCreateOptions, "SRSDIMENSION_LOC");
3570 : }
3571 :
3572 : /************************************************************************/
3573 : /* GMLFeatureCollection() */
3574 : /************************************************************************/
3575 :
3576 564 : bool OGRGMLDataSource::GMLFeatureCollection() const
3577 : {
3578 1064 : return IsGML3Output() &&
3579 1064 : CPLFetchBool(papszCreateOptions, "GML_FEATURE_COLLECTION", false);
3580 : }
|