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