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