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