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