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