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