1 /******************************************************************************
2 *
3 * Project: WFS Translator
4 * Purpose: Implements OGRWFSDataSource class
5 * Author: Even Rouault, even dot rouault at spatialys.com
6 *
7 ******************************************************************************
8 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a
11 * copy of this software and associated documentation files (the "Software"),
12 * to deal in the Software without restriction, including without limitation
13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 * and/or sell copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included
18 * in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 * DEALINGS IN THE SOFTWARE.
27 ****************************************************************************/
28
29 #include "cpl_port.h"
30 #include "ogr_wfs.h"
31 #include "ogr_api.h"
32 #include "cpl_minixml.h"
33 #include "cpl_http.h"
34 #include "gmlutils.h"
35 #include "parsexsd.h"
36 #include "ogr_swq.h"
37 #include "ogr_p.h"
38
39 #include <algorithm>
40
41 CPL_CVSID("$Id: ogrwfsdatasource.cpp fa752ad6eabafaf630a704e1892a9d837d683cb3 2021-03-06 17:04:38 +0100 Even Rouault $")
42
43 constexpr int DEFAULT_BASE_START_INDEX = 0;
44 constexpr int DEFAULT_PAGE_SIZE = 100;
45
46 typedef struct
47 {
48 const char* pszPath;
49 const char* pszMDI;
50 } MetadataItem;
51
52 static const MetadataItem asMetadata[] =
53 {
54 { "Service.Title", "TITLE" }, /*1.0 */
55 { "ServiceIdentification.Title", "TITLE" }, /* 1.1 or 2.0 */
56 { "Service.Abstract", "ABSTRACT" }, /* 1.0 */
57 { "ServiceIdentification.Abstract", "ABSTRACT" }, /* 1.1 or 2.0 */
58 { "ServiceProvider.ProviderName", "PROVIDER_NAME" }, /* 1.1 or 2.0 */
59 };
60
61 /************************************************************************/
62 /* WFSFindNode() */
63 /************************************************************************/
64
WFSFindNode(CPLXMLNode * psXML,const char * pszRootName)65 CPLXMLNode* WFSFindNode( CPLXMLNode* psXML, const char* pszRootName )
66 {
67 CPLXMLNode* psIter = psXML;
68 do
69 {
70 if (psIter->eType == CXT_Element)
71 {
72 const char* pszNodeName = psIter->pszValue;
73 const char* pszSep = strchr(pszNodeName, ':');
74 if (pszSep)
75 pszNodeName = pszSep + 1;
76 if (EQUAL(pszNodeName, pszRootName))
77 {
78 return psIter;
79 }
80 }
81 psIter = psIter->psNext;
82 } while(psIter);
83
84 psIter = psXML->psChild;
85 while(psIter)
86 {
87 if (psIter->eType == CXT_Element)
88 {
89 const char* pszNodeName = psIter->pszValue;
90 const char* pszSep = strchr(pszNodeName, ':');
91 if (pszSep)
92 pszNodeName = pszSep + 1;
93 if (EQUAL(pszNodeName, pszRootName))
94 {
95 return psIter;
96 }
97 }
98 psIter = psIter->psNext;
99 }
100 return nullptr;
101 }
102
103 /************************************************************************/
104 /* OGRWFSWrappedResultLayer */
105 /************************************************************************/
106
107 class OGRWFSWrappedResultLayer final: public OGRLayer
108 {
109 GDALDataset *poDS;
110 OGRLayer *poLayer;
111
112 public:
OGRWFSWrappedResultLayer(GDALDataset * poDSIn,OGRLayer * poLayerIn)113 OGRWFSWrappedResultLayer( GDALDataset* poDSIn, OGRLayer* poLayerIn ) :
114 poDS(poDSIn),
115 poLayer(poLayerIn)
116 {}
~OGRWFSWrappedResultLayer()117 ~OGRWFSWrappedResultLayer()
118 {
119 delete poDS;
120 }
121
ResetReading()122 virtual void ResetReading() override { poLayer->ResetReading(); }
GetNextFeature()123 virtual OGRFeature *GetNextFeature() override { return poLayer->GetNextFeature(); }
SetNextByIndex(GIntBig nIndex)124 virtual OGRErr SetNextByIndex( GIntBig nIndex ) override { return poLayer->SetNextByIndex(nIndex); }
GetFeature(GIntBig nFID)125 virtual OGRFeature *GetFeature( GIntBig nFID ) override { return poLayer->GetFeature(nFID); }
GetLayerDefn()126 virtual OGRFeatureDefn *GetLayerDefn() override { return poLayer->GetLayerDefn(); }
GetFeatureCount(int bForce=TRUE)127 virtual GIntBig GetFeatureCount( int bForce = TRUE ) override { return poLayer->GetFeatureCount(bForce); }
TestCapability(const char * pszCap)128 virtual int TestCapability( const char * pszCap ) override { return poLayer->TestCapability(pszCap); }
129 };
130
131 /************************************************************************/
132 /* OGRWFSDataSource() */
133 /************************************************************************/
134
OGRWFSDataSource()135 OGRWFSDataSource::OGRWFSDataSource() :
136 pszName(nullptr),
137 bRewriteFile(false),
138 psFileXML(nullptr),
139 papoLayers(nullptr),
140 nLayers(0),
141 bUpdate(false),
142 bGetFeatureSupportHits(false),
143 bNeedNAMESPACE(false),
144 bHasMinOperators(false),
145 bHasNullCheck(false),
146 // Advertized by deegree but not implemented.
147 bPropertyIsNotEqualToSupported(true),
148 bUseFeatureId(false), // CubeWerx doesn't like GmlObjectId.
149 bGmlObjectIdNeedsGMLPrefix(false),
150 bRequiresEnvelopeSpatialFilter(false),
151 bTransactionSupport(false),
152 papszIdGenMethods(nullptr),
153 bUseHttp10(false),
154 papszHttpOptions(nullptr),
155 bPagingAllowed(CPLTestBool(
156 CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", "OFF"))),
157 nPageSize(DEFAULT_PAGE_SIZE),
158 nBaseStartIndex(DEFAULT_BASE_START_INDEX),
159 bStandardJoinsWFS2(false),
160 bLoadMultipleLayerDefn(CPLTestBool(
161 CPLGetConfigOption("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "TRUE"))),
162 poLayerMetadataDS(nullptr),
163 poLayerMetadataLayer(nullptr),
164 poLayerGetCapabilitiesDS(nullptr),
165 poLayerGetCapabilitiesLayer(nullptr),
166 bKeepLayerNamePrefix(false),
167 bEmptyAsNull(true),
168 bInvertAxisOrderIfLatLong(true),
169 bExposeGMLId(true)
170 {
171 if( bPagingAllowed )
172 {
173 const char* pszOption = CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr);
174 if( pszOption != nullptr )
175 {
176 nPageSize = atoi(pszOption);
177 if (nPageSize <= 0)
178 nPageSize = DEFAULT_PAGE_SIZE;
179 }
180
181 pszOption = CPLGetConfigOption("OGR_WFS_BASE_START_INDEX", nullptr);
182 if( pszOption != nullptr )
183 nBaseStartIndex = atoi(pszOption);
184 }
185
186 apszGetCapabilities[0] = nullptr;
187 apszGetCapabilities[1] = nullptr;
188 }
189
190 /************************************************************************/
191 /* ~OGRWFSDataSource() */
192 /************************************************************************/
193
~OGRWFSDataSource()194 OGRWFSDataSource::~OGRWFSDataSource()
195
196 {
197 if( psFileXML )
198 {
199 if( bRewriteFile )
200 {
201 CPLSerializeXMLTreeToFile(psFileXML, pszName);
202 }
203
204 CPLDestroyXMLNode(psFileXML);
205 }
206
207 for( int i = 0; i < nLayers; i++ )
208 delete papoLayers[i];
209 CPLFree( papoLayers );
210
211 if (!osLayerMetadataTmpFileName.empty())
212 VSIUnlink(osLayerMetadataTmpFileName);
213 delete poLayerMetadataDS;
214 delete poLayerGetCapabilitiesDS;
215
216 CPLFree( pszName );
217 CSLDestroy( papszIdGenMethods );
218 CSLDestroy( papszHttpOptions );
219 }
220
221 /************************************************************************/
222 /* TestCapability() */
223 /************************************************************************/
224
TestCapability(CPL_UNUSED const char * pszCap)225 int OGRWFSDataSource::TestCapability( CPL_UNUSED const char * pszCap )
226 {
227 return FALSE;
228 }
229
230 /************************************************************************/
231 /* GetLayer() */
232 /************************************************************************/
233
GetLayer(int iLayer)234 OGRLayer *OGRWFSDataSource::GetLayer( int iLayer )
235
236 {
237 if( iLayer < 0 || iLayer >= nLayers )
238 return nullptr;
239 else
240 return papoLayers[iLayer];
241 }
242
243 /************************************************************************/
244 /* GetLayerByName() */
245 /************************************************************************/
246
GetLayerByName(const char * pszNameIn)247 OGRLayer* OGRWFSDataSource::GetLayerByName(const char* pszNameIn)
248 {
249 if ( ! pszNameIn )
250 return nullptr;
251
252 if (EQUAL(pszNameIn, "WFSLayerMetadata"))
253 {
254 if (!osLayerMetadataTmpFileName.empty())
255 return poLayerMetadataLayer;
256
257 osLayerMetadataTmpFileName = CPLSPrintf("/vsimem/tempwfs_%p/WFSLayerMetadata.csv", this);
258 osLayerMetadataCSV = "layer_name,title,abstract\n" + osLayerMetadataCSV;
259
260 VSIFCloseL(VSIFileFromMemBuffer(osLayerMetadataTmpFileName,
261 (GByte*) osLayerMetadataCSV.c_str(),
262 osLayerMetadataCSV.size(), FALSE));
263 poLayerMetadataDS = (OGRDataSource*) OGROpen(osLayerMetadataTmpFileName,
264 FALSE, nullptr);
265 if (poLayerMetadataDS)
266 poLayerMetadataLayer = poLayerMetadataDS->GetLayer(0);
267 return poLayerMetadataLayer;
268 }
269 else if (EQUAL(pszNameIn, "WFSGetCapabilities"))
270 {
271 if (poLayerGetCapabilitiesLayer != nullptr)
272 return poLayerGetCapabilitiesLayer;
273
274 GDALDriver* poMEMDrv = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName("Memory");
275 if (poMEMDrv == nullptr)
276 {
277 CPLError(CE_Failure, CPLE_AppDefined, "Cannot load 'Memory' driver");
278 return nullptr;
279 }
280
281 poLayerGetCapabilitiesDS = poMEMDrv->Create("WFSGetCapabilities", 0, 0, 0, GDT_Unknown, nullptr);
282 poLayerGetCapabilitiesLayer = poLayerGetCapabilitiesDS->CreateLayer("WFSGetCapabilities", nullptr, wkbNone, nullptr);
283 OGRFieldDefn oFDefn("content", OFTString);
284 poLayerGetCapabilitiesLayer->CreateField(&oFDefn);
285 OGRFeature* poFeature = new OGRFeature(poLayerGetCapabilitiesLayer->GetLayerDefn());
286 poFeature->SetField(0, osGetCapabilities);
287 CPL_IGNORE_RET_VAL(poLayerGetCapabilitiesLayer->CreateFeature(poFeature));
288 delete poFeature;
289
290 return poLayerGetCapabilitiesLayer;
291 }
292
293 int nIndex = GetLayerIndex(pszNameIn);
294 if (nIndex < 0)
295 return nullptr;
296 else
297 return papoLayers[nIndex];
298 }
299
300 /************************************************************************/
301 /* GetMetadataDomainList() */
302 /************************************************************************/
303
GetMetadataDomainList()304 char** OGRWFSDataSource::GetMetadataDomainList()
305 {
306 return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(),
307 TRUE,
308 "", "xml:capabilities", nullptr);
309 }
310
311 /************************************************************************/
312 /* GetMetadata() */
313 /************************************************************************/
314
GetMetadata(const char * pszDomain)315 char** OGRWFSDataSource::GetMetadata( const char * pszDomain )
316 {
317 if( pszDomain != nullptr && EQUAL(pszDomain, "xml:capabilities") )
318 {
319 apszGetCapabilities[0] = osGetCapabilities.c_str();
320 apszGetCapabilities[1] = nullptr;
321 return (char**) apszGetCapabilities;
322 }
323 return GDALDataset::GetMetadata(pszDomain);
324 }
325
326 /************************************************************************/
327 /* GetLayerIndex() */
328 /************************************************************************/
329
GetLayerIndex(const char * pszNameIn)330 int OGRWFSDataSource::GetLayerIndex(const char* pszNameIn)
331 {
332 bool bHasFoundLayerWithColon = false;
333
334 /* first a case sensitive check */
335 for( int i = 0; i < nLayers; i++ )
336 {
337 OGRWFSLayer *poLayer = papoLayers[i];
338
339 if( strcmp( pszNameIn, poLayer->GetName() ) == 0 )
340 return i;
341
342 bHasFoundLayerWithColon |= strchr( poLayer->GetName(), ':') != nullptr;
343 }
344
345 /* then case insensitive */
346 for( int i = 0; i < nLayers; i++ )
347 {
348 OGRWFSLayer *poLayer = papoLayers[i];
349
350 if( EQUAL( pszNameIn, poLayer->GetName() ) )
351 return i;
352 }
353
354 /* now try looking after the colon character */
355 if( !bKeepLayerNamePrefix &&
356 bHasFoundLayerWithColon &&
357 strchr(pszNameIn, ':') == nullptr )
358 {
359 for( int i = 0; i < nLayers; i++ )
360 {
361 OGRWFSLayer *poLayer = papoLayers[i];
362
363 const char* pszAfterColon = strchr( poLayer->GetName(), ':');
364 if( pszAfterColon && EQUAL( pszNameIn, pszAfterColon + 1 ) )
365 return i;
366 }
367 }
368
369 return -1;
370 }
371
372 /************************************************************************/
373 /* FindSubStringInsensitive() */
374 /************************************************************************/
375
FindSubStringInsensitive(const char * pszStr,const char * pszSubStr)376 const char* FindSubStringInsensitive(const char* pszStr,
377 const char* pszSubStr)
378 {
379 size_t nSubStrPos = CPLString(pszStr).ifind(pszSubStr);
380 if (nSubStrPos == std::string::npos)
381 return nullptr;
382 return pszStr + nSubStrPos;
383 }
384
385 /************************************************************************/
386 /* DetectIfGetFeatureSupportHits() */
387 /************************************************************************/
388
DetectIfGetFeatureSupportHits(CPLXMLNode * psRoot)389 static bool DetectIfGetFeatureSupportHits( CPLXMLNode* psRoot )
390 {
391 CPLXMLNode* psOperationsMetadata =
392 CPLGetXMLNode(psRoot, "OperationsMetadata");
393 if (!psOperationsMetadata)
394 {
395 CPLDebug("WFS", "Could not find <OperationsMetadata>");
396 return false;
397 }
398
399 CPLXMLNode* psChild = psOperationsMetadata->psChild;
400 while(psChild)
401 {
402 if (psChild->eType == CXT_Element &&
403 strcmp(psChild->pszValue, "Operation") == 0 &&
404 strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
405 {
406 break;
407 }
408 psChild = psChild->psNext;
409 }
410 if (!psChild)
411 {
412 CPLDebug("WFS", "Could not find <Operation name=\"GetFeature\">");
413 return false;
414 }
415
416 psChild = psChild->psChild;
417 while(psChild)
418 {
419 if (psChild->eType == CXT_Element &&
420 strcmp(psChild->pszValue, "Parameter") == 0 &&
421 strcmp(CPLGetXMLValue(psChild, "name", ""), "resultType") == 0)
422 {
423 break;
424 }
425 psChild = psChild->psNext;
426 }
427 if (!psChild)
428 {
429 CPLDebug("WFS", "Could not find <Parameter name=\"resultType\">");
430 return false;
431 }
432
433 psChild = psChild->psChild;
434 while(psChild)
435 {
436 if (psChild->eType == CXT_Element &&
437 strcmp(psChild->pszValue, "Value") == 0)
438 {
439 CPLXMLNode* psChild2 = psChild->psChild;
440 while(psChild2)
441 {
442 if (psChild2->eType == CXT_Text &&
443 strcmp(psChild2->pszValue, "hits") == 0)
444 {
445 CPLDebug("WFS", "GetFeature operation supports hits");
446 return true;
447 }
448 psChild2 = psChild2->psNext;
449 }
450 }
451 psChild = psChild->psNext;
452 }
453
454 return false;
455 }
456
457 /************************************************************************/
458 /* DetectRequiresEnvelopeSpatialFilter() */
459 /************************************************************************/
460
DetectRequiresEnvelopeSpatialFilter(CPLXMLNode * psRoot)461 bool OGRWFSDataSource::DetectRequiresEnvelopeSpatialFilter( CPLXMLNode* psRoot )
462 {
463 // This is a heuristic to detect Deegree 3 servers, such as
464 // http://deegree3-demo.deegree.org:80/deegree-utah-demo/services that are
465 // very GML3 strict, and don't like <gml:Box> in a <Filter><BBOX> request,
466 // but requires instead <gml:Envelope>, but some servers (such as MapServer)
467 // don't like <gml:Envelope> so we are obliged to detect the kind of server.
468
469 CPLXMLNode* psGeometryOperands =
470 CPLGetXMLNode(
471 psRoot,
472 "Filter_Capabilities.Spatial_Capabilities.GeometryOperands");
473 if (!psGeometryOperands)
474 {
475 return false;
476 }
477
478 int nCount = 0;
479 CPLXMLNode* psChild = psGeometryOperands->psChild;
480 while( psChild )
481 {
482 nCount++;
483 psChild = psChild->psNext;
484 }
485 // Magic number... Might be fragile.
486 return nCount == 19;
487 }
488
489 /************************************************************************/
490 /* GetPostTransactionURL() */
491 /************************************************************************/
492
GetPostTransactionURL()493 CPLString OGRWFSDataSource::GetPostTransactionURL()
494 {
495 if (!osPostTransactionURL.empty() )
496 return osPostTransactionURL;
497
498 osPostTransactionURL = osBaseURL;
499 const char* pszPostTransactionURL = osPostTransactionURL.c_str();
500 const char* pszEsperluet = strchr(pszPostTransactionURL, '?');
501 if (pszEsperluet)
502 osPostTransactionURL.resize(pszEsperluet - pszPostTransactionURL);
503
504 return osPostTransactionURL;
505 }
506
507 /************************************************************************/
508 /* DetectTransactionSupport() */
509 /************************************************************************/
510
DetectTransactionSupport(CPLXMLNode * psRoot)511 bool OGRWFSDataSource::DetectTransactionSupport( CPLXMLNode* psRoot )
512 {
513 CPLXMLNode* psTransactionWFS100 =
514 CPLGetXMLNode(psRoot, "Capability.Request.Transaction");
515 if (psTransactionWFS100)
516 {
517 CPLXMLNode* psPostURL = CPLGetXMLNode(psTransactionWFS100, "DCPType.HTTP.Post");
518 if (psPostURL)
519 {
520 const char* pszPOSTURL = CPLGetXMLValue(psPostURL, "onlineResource", nullptr);
521 if (pszPOSTURL)
522 {
523 osPostTransactionURL = pszPOSTURL;
524 }
525 }
526
527 bTransactionSupport = true;
528 return true;
529 }
530
531 CPLXMLNode* psOperationsMetadata =
532 CPLGetXMLNode(psRoot, "OperationsMetadata");
533 if (!psOperationsMetadata)
534 {
535 return false;
536 }
537
538 CPLXMLNode* psChild = psOperationsMetadata->psChild;
539 while(psChild)
540 {
541 if (psChild->eType == CXT_Element &&
542 strcmp(psChild->pszValue, "Operation") == 0 &&
543 strcmp(CPLGetXMLValue(psChild, "name", ""), "Transaction") == 0)
544 {
545 break;
546 }
547 psChild = psChild->psNext;
548 }
549 if (!psChild)
550 {
551 CPLDebug("WFS", "No transaction support");
552 return false;
553 }
554
555 bTransactionSupport = true;
556 CPLDebug("WFS", "Transaction support !");
557
558 CPLXMLNode* psPostURL = CPLGetXMLNode(psChild, "DCP.HTTP.Post");
559 if (psPostURL)
560 {
561 const char* pszPOSTURL = CPLGetXMLValue(psPostURL, "href", nullptr);
562 if (pszPOSTURL)
563 osPostTransactionURL = pszPOSTURL;
564 }
565
566 psChild = psChild->psChild;
567 while(psChild)
568 {
569 if (psChild->eType == CXT_Element &&
570 strcmp(psChild->pszValue, "Parameter") == 0 &&
571 strcmp(CPLGetXMLValue(psChild, "name", ""), "idgen") == 0)
572 {
573 break;
574 }
575 psChild = psChild->psNext;
576 }
577 if (!psChild)
578 {
579 papszIdGenMethods = CSLAddString(nullptr, "GenerateNew");
580 return true;
581 }
582
583 psChild = psChild->psChild;
584 while(psChild)
585 {
586 if (psChild->eType == CXT_Element &&
587 strcmp(psChild->pszValue, "Value") == 0)
588 {
589 CPLXMLNode* psChild2 = psChild->psChild;
590 while(psChild2)
591 {
592 if (psChild2->eType == CXT_Text)
593 {
594 papszIdGenMethods = CSLAddString(papszIdGenMethods,
595 psChild2->pszValue);
596 }
597 psChild2 = psChild2->psNext;
598 }
599 }
600 psChild = psChild->psNext;
601 }
602
603 return true;
604 }
605
606 /************************************************************************/
607 /* DetectSupportPagingWFS2() */
608 /************************************************************************/
609
DetectSupportPagingWFS2(CPLXMLNode * psRoot)610 bool OGRWFSDataSource::DetectSupportPagingWFS2( CPLXMLNode* psRoot )
611 {
612 const char* pszPagingAllowed = CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", nullptr);
613 if( pszPagingAllowed != nullptr && !CPLTestBool(pszPagingAllowed) )
614 return false;
615
616 CPLXMLNode* psOperationsMetadata =
617 CPLGetXMLNode(psRoot, "OperationsMetadata");
618 if (!psOperationsMetadata)
619 {
620 return false;
621 }
622
623 CPLXMLNode* psChild = psOperationsMetadata->psChild;
624 while(psChild)
625 {
626 if (psChild->eType == CXT_Element &&
627 strcmp(psChild->pszValue, "Constraint") == 0 &&
628 strcmp(CPLGetXMLValue(psChild, "name", ""), "ImplementsResultPaging") == 0)
629 {
630 if( !EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE") )
631 {
632 psChild = nullptr;
633 break;
634 }
635 break;
636 }
637 psChild = psChild->psNext;
638 }
639 if (!psChild)
640 {
641 CPLDebug("WFS", "No paging support");
642 return false;
643 }
644
645 psChild = psOperationsMetadata->psChild;
646 while(psChild)
647 {
648 if (psChild->eType == CXT_Element &&
649 strcmp(psChild->pszValue, "Operation") == 0 &&
650 strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
651 {
652 break;
653 }
654 psChild = psChild->psNext;
655 }
656 if (psChild && CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr) == nullptr)
657 {
658 psChild = psChild->psChild;
659 while(psChild)
660 {
661 if (psChild->eType == CXT_Element &&
662 strcmp(psChild->pszValue, "Constraint") == 0 &&
663 strcmp(CPLGetXMLValue(psChild, "name", ""), "CountDefault") == 0)
664 {
665 int nVal = atoi(CPLGetXMLValue(psChild, "DefaultValue", "0"));
666 if( nVal > 0 )
667 nPageSize = nVal;
668
669 break;
670 }
671 psChild = psChild->psNext;
672 }
673 }
674 const char* pszOption = CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr);
675 if( pszOption != nullptr )
676 {
677 nPageSize = atoi(pszOption);
678 if (nPageSize <= 0)
679 nPageSize = DEFAULT_PAGE_SIZE;
680 }
681
682 CPLDebug("WFS", "Paging support with page size %d", nPageSize);
683 bPagingAllowed = true;
684
685 return true;
686 }
687
688 /************************************************************************/
689 /* DetectSupportStandardJoinsWFS2() */
690 /************************************************************************/
691
DetectSupportStandardJoinsWFS2(CPLXMLNode * psRoot)692 bool OGRWFSDataSource::DetectSupportStandardJoinsWFS2(CPLXMLNode* psRoot)
693 {
694 CPLXMLNode* psOperationsMetadata =
695 CPLGetXMLNode(psRoot, "OperationsMetadata");
696 if( !psOperationsMetadata )
697 {
698 return false;
699 }
700
701 CPLXMLNode* psChild = psOperationsMetadata->psChild;
702 while(psChild)
703 {
704 if (psChild->eType == CXT_Element &&
705 strcmp(psChild->pszValue, "Constraint") == 0 &&
706 strcmp(CPLGetXMLValue(psChild, "name", ""), "ImplementsStandardJoins") == 0)
707 {
708 if( !EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE") )
709 {
710 psChild = nullptr;
711 break;
712 }
713 break;
714 }
715 psChild = psChild->psNext;
716 }
717 if (!psChild)
718 {
719 CPLDebug("WFS", "No ImplementsStandardJoins support");
720 return false;
721 }
722 bStandardJoinsWFS2 = true;
723 return true;
724 }
725
726 /************************************************************************/
727 /* FindComparisonOperator() */
728 /************************************************************************/
729
FindComparisonOperator(CPLXMLNode * psNode,const char * pszVal)730 static bool FindComparisonOperator( CPLXMLNode* psNode, const char* pszVal )
731 {
732 CPLXMLNode* psChild = psNode->psChild;
733 while(psChild)
734 {
735 if (psChild->eType == CXT_Element &&
736 strcmp(psChild->pszValue, "ComparisonOperator") == 0)
737 {
738 if (strcmp(CPLGetXMLValue(psChild, nullptr, ""), pszVal) == 0)
739 return true;
740
741 /* For WFS 2.0.0 */
742 const char* pszName = CPLGetXMLValue(psChild, "name", nullptr);
743 if (pszName != nullptr && STARTS_WITH(pszName, "PropertyIs") &&
744 strcmp(pszName + 10, pszVal) == 0)
745 return true;
746 }
747 psChild = psChild->psNext;
748 }
749 return false;
750 }
751
752 /************************************************************************/
753 /* LoadFromFile() */
754 /************************************************************************/
755
LoadFromFile(const char * pszFilename)756 CPLXMLNode* OGRWFSDataSource::LoadFromFile( const char * pszFilename )
757 {
758 VSIStatBufL sStatBuf;
759 if (VSIStatExL( pszFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG ) != 0 ||
760 VSI_ISDIR(sStatBuf.st_mode))
761 return nullptr;
762
763 VSILFILE *fp = VSIFOpenL( pszFilename, "rb" );
764
765 if( fp == nullptr )
766 return nullptr;
767
768 char achHeader[1024] = {};
769 const int nRead =
770 static_cast<int>(VSIFReadL( achHeader, 1, sizeof(achHeader) - 1, fp ));
771 if( nRead == 0 )
772 {
773 VSIFCloseL( fp );
774 return nullptr;
775 }
776 achHeader[nRead] = 0;
777
778 if( !STARTS_WITH_CI(achHeader, "<OGRWFSDataSource>") &&
779 strstr(achHeader,"<WFS_Capabilities") == nullptr &&
780 strstr(achHeader,"<wfs:WFS_Capabilities") == nullptr)
781 {
782 VSIFCloseL( fp );
783 return nullptr;
784 }
785
786 /* -------------------------------------------------------------------- */
787 /* It is the right file, now load the full XML definition. */
788 /* -------------------------------------------------------------------- */
789 VSIFSeekL( fp, 0, SEEK_END );
790 const int nLen = (int) VSIFTellL( fp );
791 VSIFSeekL( fp, 0, SEEK_SET );
792
793 char* pszXML = (char *) VSI_MALLOC_VERBOSE(nLen+1);
794 if (pszXML == nullptr)
795 {
796 VSIFCloseL( fp );
797 return nullptr;
798 }
799 pszXML[nLen] = '\0';
800 if( ((int) VSIFReadL( pszXML, 1, nLen, fp )) != nLen )
801 {
802 CPLFree( pszXML );
803 VSIFCloseL( fp );
804
805 return nullptr;
806 }
807 VSIFCloseL( fp );
808
809 if (strstr(pszXML, "CubeWerx"))
810 {
811 /* At least true for CubeWerx Suite 4.15.1 */
812 bUseFeatureId = true;
813 }
814 else if (strstr(pszXML, "deegree"))
815 {
816 bGmlObjectIdNeedsGMLPrefix = true;
817 }
818
819 CPLXMLNode* psXML = CPLParseXMLString( pszXML );
820 CPLFree( pszXML );
821
822 return psXML;
823 }
824
825 /************************************************************************/
826 /* SendGetCapabilities() */
827 /************************************************************************/
828
SendGetCapabilities(const char * pszBaseURL,CPLString & osTypeName)829 CPLHTTPResult* OGRWFSDataSource::SendGetCapabilities(const char* pszBaseURL,
830 CPLString& osTypeName)
831 {
832 CPLString osURL(pszBaseURL);
833
834 osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
835 osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
836 osTypeName = CPLURLGetValue(osURL, "TYPENAME");
837 if( osTypeName.empty() )
838 osTypeName = CPLURLGetValue(osURL, "TYPENAMES");
839 osURL = CPLURLAddKVP(osURL, "TYPENAME", nullptr);
840 osURL = CPLURLAddKVP(osURL, "TYPENAMES", nullptr);
841 osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
842 osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
843 osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
844 osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", nullptr);
845
846 CPLDebug("WFS", "%s", osURL.c_str());
847
848 CPLHTTPResult* psResult = HTTPFetch( osURL, nullptr);
849 if (psResult == nullptr)
850 {
851 return nullptr;
852 }
853
854 if (strstr((const char*)psResult->pabyData,
855 "<ServiceExceptionReport") != nullptr ||
856 strstr((const char*)psResult->pabyData,
857 "<ows:ExceptionReport") != nullptr ||
858 strstr((const char*)psResult->pabyData,
859 "<ExceptionReport") != nullptr)
860 {
861 CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
862 psResult->pabyData);
863 CPLHTTPDestroyResult(psResult);
864 return nullptr;
865 }
866
867 return psResult;
868 }
869
870 /************************************************************************/
871 /* Open() */
872 /************************************************************************/
873
Open(const char * pszFilename,int bUpdateIn,char ** papszOpenOptionsIn)874 int OGRWFSDataSource::Open( const char * pszFilename, int bUpdateIn,
875 char** papszOpenOptionsIn )
876
877 {
878 bUpdate = CPL_TO_BOOL(bUpdateIn);
879 CPLFree(pszName);
880 pszName = CPLStrdup(pszFilename);
881
882 CPLXMLNode* psWFSCapabilities = nullptr;
883 CPLXMLNode* psXML = LoadFromFile(pszFilename);
884 CPLString osTypeName;
885 const char* pszBaseURL = nullptr;
886
887 bEmptyAsNull = CPLFetchBool(papszOpenOptionsIn, "EMPTY_AS_NULL", true);
888
889 if (psXML == nullptr)
890 {
891 if (!STARTS_WITH_CI(pszFilename, "WFS:") &&
892 FindSubStringInsensitive(pszFilename, "SERVICE=WFS") == nullptr)
893 {
894 return FALSE;
895 }
896
897 pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
898 if( pszBaseURL == nullptr )
899 {
900 pszBaseURL = pszFilename;
901 if (STARTS_WITH_CI(pszFilename, "WFS:"))
902 pszBaseURL += 4;
903 }
904
905 osBaseURL = pszBaseURL;
906
907 if (!STARTS_WITH(pszBaseURL, "http://") &&
908 !STARTS_WITH(pszBaseURL, "https://") &&
909 !STARTS_WITH(pszBaseURL, "/vsimem/"))
910 return FALSE;
911
912 CPLString strOriginalTypeName = "";
913 CPLHTTPResult* psResult = SendGetCapabilities(pszBaseURL, strOriginalTypeName);
914 osTypeName = WFS_DecodeURL(strOriginalTypeName);
915 if (psResult == nullptr)
916 {
917 return FALSE;
918 }
919
920 if (strstr((const char*) psResult->pabyData, "CubeWerx"))
921 {
922 /* At least true for CubeWerx Suite 4.15.1 */
923 bUseFeatureId = true;
924 }
925 else if (strstr((const char*) psResult->pabyData, "deegree"))
926 {
927 bGmlObjectIdNeedsGMLPrefix = true;
928 }
929
930 psXML = CPLParseXMLString( (const char*) psResult->pabyData );
931 if (psXML == nullptr)
932 {
933 CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
934 psResult->pabyData);
935 CPLHTTPDestroyResult(psResult);
936 return FALSE;
937 }
938 osGetCapabilities = (const char*) psResult->pabyData;
939
940 CPLHTTPDestroyResult(psResult);
941 }
942 else if ( WFSFindNode( psXML, "OGRWFSDataSource" ) == nullptr &&
943 WFSFindNode( psXML, "WFS_Capabilities" ) != nullptr )
944 {
945 /* This is directly the Capabilities document */
946 char* pszXML = CPLSerializeXMLTree(WFSFindNode( psXML, "WFS_Capabilities" ));
947 osGetCapabilities = pszXML;
948 CPLFree(pszXML);
949 }
950 else
951 {
952 CPLXMLNode* psRoot = WFSFindNode( psXML, "OGRWFSDataSource" );
953 if (psRoot == nullptr)
954 {
955 CPLError(CE_Failure, CPLE_AppDefined,
956 "Cannot find <OGRWFSDataSource>");
957 CPLDestroyXMLNode( psXML );
958 return FALSE;
959 }
960
961 pszBaseURL = CPLGetXMLValue(psRoot, "URL", nullptr);
962 if (pszBaseURL == nullptr)
963 {
964 CPLError(CE_Failure, CPLE_AppDefined,
965 "Cannot find <URL>");
966 CPLDestroyXMLNode( psXML );
967 return FALSE;
968 }
969 osBaseURL = pszBaseURL;
970
971 /* -------------------------------------------------------------------- */
972 /* Capture other parameters. */
973 /* -------------------------------------------------------------------- */
974 const char *pszParam = CPLGetXMLValue( psRoot, "Timeout", nullptr );
975 if( pszParam )
976 papszHttpOptions =
977 CSLSetNameValue(papszHttpOptions,
978 "TIMEOUT", pszParam );
979
980 pszParam = CPLGetXMLValue( psRoot, "HTTPAUTH", nullptr );
981 if( pszParam )
982 papszHttpOptions =
983 CSLSetNameValue( papszHttpOptions,
984 "HTTPAUTH", pszParam );
985
986 pszParam = CPLGetXMLValue( psRoot, "USERPWD", nullptr );
987 if( pszParam )
988 papszHttpOptions =
989 CSLSetNameValue( papszHttpOptions,
990 "USERPWD", pszParam );
991
992 pszParam = CPLGetXMLValue( psRoot, "COOKIE", nullptr );
993 if( pszParam )
994 papszHttpOptions =
995 CSLSetNameValue( papszHttpOptions,
996 "COOKIE", pszParam );
997
998 pszParam = CPLGetXMLValue( psRoot, "Version", nullptr );
999 if( pszParam )
1000 osVersion = pszParam;
1001
1002 pszParam = CPLGetXMLValue( psRoot, "PagingAllowed", nullptr );
1003 if( pszParam )
1004 bPagingAllowed = CPLTestBool(pszParam);
1005
1006 pszParam = CPLGetXMLValue( psRoot, "PageSize", nullptr );
1007 if( pszParam )
1008 {
1009 nPageSize = atoi(pszParam);
1010 if (nPageSize <= 0)
1011 nPageSize = DEFAULT_PAGE_SIZE;
1012 }
1013
1014 pszParam = CPLGetXMLValue( psRoot, "BaseStartIndex", nullptr );
1015 if( pszParam )
1016 nBaseStartIndex = atoi(pszParam);
1017
1018 CPLString strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAME");
1019 if( strOriginalTypeName.empty() )
1020 strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAMES");
1021 osTypeName = WFS_DecodeURL(strOriginalTypeName);
1022
1023 psWFSCapabilities = WFSFindNode( psRoot, "WFS_Capabilities" );
1024 if (psWFSCapabilities == nullptr)
1025 {
1026 CPLHTTPResult* psResult = SendGetCapabilities(pszBaseURL, strOriginalTypeName);
1027 if (psResult == nullptr)
1028 {
1029 CPLDestroyXMLNode( psXML );
1030 return FALSE;
1031 }
1032
1033 CPLXMLNode* psXML2 = CPLParseXMLString( (const char*) psResult->pabyData );
1034 if (psXML2 == nullptr)
1035 {
1036 CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
1037 psResult->pabyData);
1038 CPLHTTPDestroyResult(psResult);
1039 CPLDestroyXMLNode( psXML );
1040 return FALSE;
1041 }
1042
1043 CPLHTTPDestroyResult(psResult);
1044
1045 psWFSCapabilities = WFSFindNode( psXML2, "WFS_Capabilities" );
1046 if (psWFSCapabilities == nullptr )
1047 {
1048 CPLError(CE_Failure, CPLE_AppDefined,
1049 "Cannot find <WFS_Capabilities>");
1050 CPLDestroyXMLNode( psXML );
1051 CPLDestroyXMLNode( psXML2 );
1052 return FALSE;
1053 }
1054
1055 CPLAddXMLChild(psXML, CPLCloneXMLTree(psWFSCapabilities));
1056
1057 const bool bOK =
1058 CPL_TO_BOOL(CPLSerializeXMLTreeToFile(psXML, pszFilename));
1059
1060 CPLDestroyXMLNode( psXML );
1061 CPLDestroyXMLNode( psXML2 );
1062
1063 if( bOK )
1064 return Open(pszFilename, bUpdate, papszOpenOptionsIn);
1065
1066 return FALSE;
1067 }
1068 else
1069 {
1070 psFileXML = psXML;
1071
1072 /* To avoid to have nodes after WFSCapabilities */
1073 CPLXMLNode* psAfterWFSCapabilities = psWFSCapabilities->psNext;
1074 psWFSCapabilities->psNext = nullptr;
1075 char* pszXML = CPLSerializeXMLTree(psWFSCapabilities);
1076 psWFSCapabilities->psNext = psAfterWFSCapabilities;
1077 osGetCapabilities = pszXML;
1078 CPLFree(pszXML);
1079 }
1080 }
1081
1082 bInvertAxisOrderIfLatLong =
1083 CPLTestBool(CSLFetchNameValueDef(papszOpenOptionsIn,
1084 "INVERT_AXIS_ORDER_IF_LAT_LONG",
1085 CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")));
1086 osConsiderEPSGAsURN =
1087 CSLFetchNameValueDef(papszOpenOptionsIn,
1088 "CONSIDER_EPSG_AS_URN",
1089 CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
1090 bExposeGMLId =
1091 CPLTestBool(CSLFetchNameValueDef(papszOpenOptionsIn,
1092 "EXPOSE_GML_ID",
1093 CPLGetConfigOption("GML_EXPOSE_GML_ID", "YES")));
1094
1095 CPLXMLNode* psStrippedXML = CPLCloneXMLTree(psXML);
1096 CPLStripXMLNamespace( psStrippedXML, nullptr, TRUE );
1097 psWFSCapabilities = CPLGetXMLNode( psStrippedXML, "=WFS_Capabilities" );
1098 if (psWFSCapabilities == nullptr)
1099 {
1100 psWFSCapabilities = CPLGetXMLNode( psStrippedXML, "=OGRWFSDataSource.WFS_Capabilities" );
1101 }
1102 if (psWFSCapabilities == nullptr)
1103 {
1104 CPLError(CE_Failure, CPLE_AppDefined,
1105 "Cannot find <WFS_Capabilities>");
1106 if (!psFileXML) CPLDestroyXMLNode( psXML );
1107 CPLDestroyXMLNode( psStrippedXML );
1108 return FALSE;
1109 }
1110
1111 if (pszBaseURL == nullptr)
1112 {
1113 /* This is directly the Capabilities document */
1114 pszBaseURL = CPLGetXMLValue( psWFSCapabilities, "OperationsMetadata.Operation.DCP.HTTP.Get.href", nullptr );
1115 if (pszBaseURL == nullptr) /* WFS 1.0.0 variant */
1116 pszBaseURL = CPLGetXMLValue( psWFSCapabilities, "Capability.Request.GetCapabilities.DCPType.HTTP.Get.onlineResource", nullptr );
1117
1118 if (pszBaseURL == nullptr)
1119 {
1120 CPLError(CE_Failure, CPLE_AppDefined,
1121 "Cannot find base URL");
1122 if (!psFileXML) CPLDestroyXMLNode( psXML );
1123 CPLDestroyXMLNode( psStrippedXML );
1124 return FALSE;
1125 }
1126
1127 osBaseURL = pszBaseURL;
1128 }
1129
1130 pszBaseURL = nullptr;
1131
1132 for( int i=0; i < (int)(sizeof(asMetadata) / sizeof(asMetadata[0])); i++ )
1133 {
1134 const char* pszVal = CPLGetXMLValue( psWFSCapabilities, asMetadata[i].pszPath, nullptr );
1135 if( pszVal )
1136 SetMetadataItem(asMetadata[i].pszMDI, pszVal);
1137 }
1138
1139 if( osVersion.empty() )
1140 osVersion = CPLGetXMLValue(psWFSCapabilities, "version", "1.0.0");
1141 if( strcmp(osVersion.c_str(), "1.0.0") == 0 )
1142 {
1143 bUseFeatureId = true;
1144 }
1145 else
1146 {
1147 /* Some servers happen to support RESULTTYPE=hits in 1.0.0, but there */
1148 /* is no way to advertises this */
1149 if (atoi(osVersion) >= 2)
1150 bGetFeatureSupportHits = true; /* WFS >= 2.0.0 supports hits */
1151 else
1152 bGetFeatureSupportHits = DetectIfGetFeatureSupportHits(psWFSCapabilities);
1153 bRequiresEnvelopeSpatialFilter =
1154 DetectRequiresEnvelopeSpatialFilter(psWFSCapabilities);
1155 }
1156
1157 if ( atoi(osVersion) >= 2 )
1158 {
1159 CPLString osMaxFeatures = CPLURLGetValue(osBaseURL, "COUNT" );
1160 /* Ok, people are used to MAXFEATURES, so be nice to recognize it if it is used for WFS 2.0 ... */
1161 if (osMaxFeatures.empty() )
1162 {
1163 osMaxFeatures = CPLURLGetValue(osBaseURL, "MAXFEATURES");
1164 if( !osMaxFeatures.empty() &&
1165 CPLTestBool(CPLGetConfigOption("OGR_WFS_FIX_MAXFEATURES", "YES")) )
1166 {
1167 CPLDebug("WFS", "MAXFEATURES wrongly used for WFS 2.0. Using COUNT instead");
1168 osBaseURL = CPLURLAddKVP(osBaseURL, "MAXFEATURES", nullptr);
1169 osBaseURL = CPLURLAddKVP(osBaseURL, "COUNT", osMaxFeatures);
1170 }
1171 }
1172
1173 DetectSupportPagingWFS2(psWFSCapabilities);
1174 DetectSupportStandardJoinsWFS2(psWFSCapabilities);
1175 }
1176
1177 DetectTransactionSupport(psWFSCapabilities);
1178
1179 if( bUpdate && !bTransactionSupport )
1180 {
1181 CPLError(CE_Failure, CPLE_AppDefined,
1182 "Server is read-only WFS; no WFS-T feature advertized");
1183 if (!psFileXML) CPLDestroyXMLNode( psXML );
1184 CPLDestroyXMLNode( psStrippedXML );
1185 return FALSE;
1186 }
1187
1188 CPLXMLNode* psFilterCap = CPLGetXMLNode(psWFSCapabilities, "Filter_Capabilities.Scalar_Capabilities");
1189 if (psFilterCap)
1190 {
1191 bHasMinOperators =
1192 CPLGetXMLNode(psFilterCap, "LogicalOperators") != nullptr ||
1193 CPLGetXMLNode(psFilterCap, "Logical_Operators") != nullptr;
1194 if (CPLGetXMLNode(psFilterCap, "ComparisonOperators"))
1195 psFilterCap = CPLGetXMLNode(psFilterCap, "ComparisonOperators");
1196 else if (CPLGetXMLNode(psFilterCap, "Comparison_Operators"))
1197 psFilterCap = CPLGetXMLNode(psFilterCap, "Comparison_Operators");
1198 else
1199 psFilterCap = nullptr;
1200 if (psFilterCap)
1201 {
1202 if (CPLGetXMLNode(psFilterCap, "Simple_Comparisons") == nullptr)
1203 {
1204 bHasMinOperators &= FindComparisonOperator(psFilterCap, "LessThan");
1205 bHasMinOperators &= FindComparisonOperator(psFilterCap, "GreaterThan");
1206 if (atoi(osVersion) >= 2)
1207 {
1208 bHasMinOperators &= FindComparisonOperator(psFilterCap, "LessThanOrEqualTo");
1209 bHasMinOperators &= FindComparisonOperator(psFilterCap, "GreaterThanOrEqualTo");
1210 }
1211 else
1212 {
1213 bHasMinOperators &= FindComparisonOperator(psFilterCap, "LessThanEqualTo");
1214 bHasMinOperators &= FindComparisonOperator(psFilterCap, "GreaterThanEqualTo");
1215 }
1216 bHasMinOperators &= FindComparisonOperator(psFilterCap, "EqualTo");
1217 bHasMinOperators &= FindComparisonOperator(psFilterCap, "NotEqualTo");
1218 bHasMinOperators &= FindComparisonOperator(psFilterCap, "Like");
1219 }
1220 else
1221 {
1222 bHasMinOperators &= CPLGetXMLNode(psFilterCap, "Simple_Comparisons") != nullptr &&
1223 CPLGetXMLNode(psFilterCap, "Like") != nullptr;
1224 }
1225 bHasNullCheck = FindComparisonOperator(psFilterCap, "NullCheck") ||
1226 FindComparisonOperator(psFilterCap, "Null") || /* WFS 2.0.0 */
1227 CPLGetXMLNode(psFilterCap, "NullCheck") != nullptr;
1228 }
1229 else
1230 {
1231 bHasMinOperators = false;
1232 }
1233 }
1234
1235 CPLXMLNode* psChild = CPLGetXMLNode(psWFSCapabilities, "FeatureTypeList");
1236 if (psChild == nullptr)
1237 {
1238 CPLError(CE_Failure, CPLE_AppDefined,
1239 "Cannot find <FeatureTypeList>");
1240 if (!psFileXML) CPLDestroyXMLNode( psXML );
1241 CPLDestroyXMLNode( psStrippedXML );
1242 return FALSE;
1243 }
1244
1245 /* Check if there are layer names whose identical except their prefix */
1246 std::set<CPLString> aosSetLayerNames;
1247 for( CPLXMLNode* psChildIter = psChild->psChild;
1248 psChildIter != nullptr;
1249 psChildIter = psChildIter->psNext)
1250 {
1251 if (psChildIter->eType == CXT_Element &&
1252 strcmp(psChildIter->pszValue, "FeatureType") == 0)
1253 {
1254 const char* l_pszName = CPLGetXMLValue(psChildIter, "Name", nullptr);
1255 if (l_pszName != nullptr)
1256 {
1257 const char* pszShortName = strchr(l_pszName, ':');
1258 if (pszShortName)
1259 l_pszName = pszShortName + 1;
1260 if (aosSetLayerNames.find(l_pszName) != aosSetLayerNames.end())
1261 {
1262 bKeepLayerNamePrefix = true;
1263 CPLDebug(
1264 "WFS",
1265 "At least 2 layers have names that are only "
1266 "distinguishable by keeping the prefix");
1267 break;
1268 }
1269 aosSetLayerNames.insert(l_pszName);
1270 }
1271 }
1272 }
1273
1274 char** papszTypenames = nullptr;
1275 if (!osTypeName.empty())
1276 papszTypenames = CSLTokenizeStringComplex( osTypeName, ",", FALSE, FALSE );
1277
1278 for( CPLXMLNode* psChildIter = psChild->psChild;
1279 psChildIter != nullptr;
1280 psChildIter = psChildIter->psNext )
1281 {
1282 if (psChildIter->eType == CXT_Element &&
1283 strcmp(psChildIter->pszValue, "FeatureType") == 0)
1284 {
1285 const char* pszNS = nullptr;
1286 const char* pszNSVal = nullptr;
1287 CPLXMLNode* psFeatureTypeIter = psChildIter->psChild;
1288 while(psFeatureTypeIter != nullptr)
1289 {
1290 if (psFeatureTypeIter->eType == CXT_Attribute)
1291 {
1292 pszNS = psFeatureTypeIter->pszValue;
1293 pszNSVal = psFeatureTypeIter->psChild->pszValue;
1294 }
1295 psFeatureTypeIter = psFeatureTypeIter->psNext;
1296 }
1297
1298 const char* l_pszName = CPLGetXMLValue(psChildIter, "Name", nullptr);
1299 const char* pszTitle = CPLGetXMLValue(psChildIter, "Title", nullptr);
1300 const char* pszAbstract = CPLGetXMLValue(psChildIter, "Abstract", nullptr);
1301 if (l_pszName != nullptr &&
1302 (papszTypenames == nullptr ||
1303 CSLFindString(papszTypenames, l_pszName) != -1))
1304 {
1305 const char* pszDefaultSRS =
1306 CPLGetXMLValue(psChildIter, "DefaultSRS", nullptr);
1307 if (pszDefaultSRS == nullptr)
1308 pszDefaultSRS = CPLGetXMLValue(psChildIter, "SRS", nullptr);
1309 if (pszDefaultSRS == nullptr)
1310 pszDefaultSRS = CPLGetXMLValue(psChildIter, "DefaultCRS", nullptr); /* WFS 2.0.0 */
1311
1312 CPLXMLNode* psOutputFormats = CPLGetXMLNode(psChildIter, "OutputFormats");
1313 CPLString osOutputFormat;
1314 if (psOutputFormats)
1315 {
1316 std::vector<CPLString> osFormats;
1317 CPLXMLNode* psOutputFormatIter = psOutputFormats->psChild;
1318 while(psOutputFormatIter)
1319 {
1320 if (psOutputFormatIter->eType == CXT_Element &&
1321 EQUAL(psOutputFormatIter->pszValue, "Format") &&
1322 psOutputFormatIter->psChild != nullptr &&
1323 psOutputFormatIter->psChild->eType == CXT_Text)
1324 {
1325 osFormats.push_back(psOutputFormatIter->psChild->pszValue);
1326 }
1327 psOutputFormatIter = psOutputFormatIter->psNext;
1328 }
1329
1330 if (strcmp(osVersion.c_str(), "1.1.0") == 0 && !osFormats.empty())
1331 {
1332 bool bFoundGML31 = false;
1333 for(size_t i=0;i<osFormats.size();i++)
1334 {
1335 if (strstr(osFormats[i].c_str(), "3.1") != nullptr)
1336 {
1337 bFoundGML31 = true;
1338 break;
1339 }
1340 }
1341
1342 /* If we didn't find any mention to GML 3.1, then arbitrarily */
1343 /* use the first output format */
1344 if( !bFoundGML31 )
1345 osOutputFormat = osFormats[0].c_str();
1346 }
1347 }
1348
1349 OGRSpatialReference* poSRS = nullptr;
1350 bool bAxisOrderAlreadyInverted = false;
1351
1352 /* If a SRSNAME parameter has been encoded in the URL, use it as the SRS */
1353 CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME");
1354 if (!osSRSName.empty())
1355 {
1356 pszDefaultSRS = osSRSName.c_str();
1357 }
1358
1359 // EPSG:404000 is a GeoServer joke to indicate a unknown SRS
1360 // https://osgeo-org.atlassian.net/browse/GEOS-8993
1361 if (pszDefaultSRS &&
1362 !EQUAL(pszDefaultSRS, "EPSG:404000") &&
1363 !EQUAL(pszDefaultSRS, "urn:ogc:def:crs:EPSG::404000"))
1364 {
1365 OGRSpatialReference oSRS;
1366 if (oSRS.SetFromUserInput(pszDefaultSRS) == OGRERR_NONE)
1367 {
1368 poSRS = oSRS.Clone();
1369 poSRS->SetAxisMappingStrategy(
1370 bInvertAxisOrderIfLatLong ? OAMS_TRADITIONAL_GIS_ORDER : OAMS_AUTHORITY_COMPLIANT);
1371 if( GML_IsSRSLatLongOrder(pszDefaultSRS) &&
1372 bInvertAxisOrderIfLatLong )
1373 {
1374 bAxisOrderAlreadyInverted = true;
1375 }
1376 }
1377 }
1378
1379 CPLXMLNode* psBBox = nullptr;
1380 CPLXMLNode* psLatLongBBox = nullptr;
1381 /* bool bFoundBBox = false; */
1382 double dfMinX = 0.0;
1383 double dfMinY = 0.0;
1384 double dfMaxX = 0.0;
1385 double dfMaxY = 0.0;
1386 if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) != nullptr)
1387 {
1388 const char* pszLC = CPLGetXMLValue(psBBox, "LowerCorner", nullptr);
1389 const char* pszUC = CPLGetXMLValue(psBBox, "UpperCorner", nullptr);
1390 if (pszLC != nullptr && pszUC != nullptr)
1391 {
1392 CPLString osConcat(pszLC);
1393 osConcat += " ";
1394 osConcat += pszUC;
1395 char **papszTokens = CSLTokenizeStringComplex(
1396 osConcat, " ,", FALSE, FALSE );
1397 if (CSLCount(papszTokens) == 4)
1398 {
1399 // bFoundBBox = true;
1400 dfMinX = CPLAtof(papszTokens[0]);
1401 dfMinY = CPLAtof(papszTokens[1]);
1402 dfMaxX = CPLAtof(papszTokens[2]);
1403 dfMaxY = CPLAtof(papszTokens[3]);
1404 }
1405 CSLDestroy(papszTokens);
1406 }
1407 }
1408 else if ((psLatLongBBox = CPLGetXMLNode(psChildIter,
1409 "LatLongBoundingBox")) != nullptr)
1410 {
1411 const char* pszMinX =
1412 CPLGetXMLValue(psLatLongBBox, "minx", nullptr);
1413 const char* pszMinY =
1414 CPLGetXMLValue(psLatLongBBox, "miny", nullptr);
1415 const char* pszMaxX =
1416 CPLGetXMLValue(psLatLongBBox, "maxx", nullptr);
1417 const char* pszMaxY =
1418 CPLGetXMLValue(psLatLongBBox, "maxy", nullptr);
1419 if (pszMinX != nullptr && pszMinY != nullptr &&
1420 pszMaxX != nullptr && pszMaxY != nullptr)
1421 {
1422 // bFoundBBox = true;
1423 dfMinX = CPLAtof(pszMinX);
1424 dfMinY = CPLAtof(pszMinY);
1425 dfMaxX = CPLAtof(pszMaxX);
1426 dfMaxY = CPLAtof(pszMaxY);
1427 }
1428 }
1429
1430 char* pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV);
1431 osLayerMetadataCSV += pszCSVEscaped;
1432 CPLFree(pszCSVEscaped);
1433
1434 osLayerMetadataCSV += ",";
1435 if (pszTitle)
1436 {
1437 pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV);
1438 osLayerMetadataCSV += pszCSVEscaped;
1439 CPLFree(pszCSVEscaped);
1440 }
1441 osLayerMetadataCSV += ",";
1442 if (pszAbstract)
1443 {
1444 pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV);
1445 osLayerMetadataCSV += pszCSVEscaped;
1446 CPLFree(pszCSVEscaped);
1447 }
1448 osLayerMetadataCSV += "\n";
1449
1450 OGRWFSLayer* poLayer = new OGRWFSLayer(
1451 this, poSRS, bAxisOrderAlreadyInverted,
1452 osBaseURL, l_pszName, pszNS, pszNSVal);
1453 if (!osOutputFormat.empty() )
1454 poLayer->SetRequiredOutputFormat(osOutputFormat);
1455
1456 if( pszTitle )
1457 poLayer->SetMetadataItem("TITLE", pszTitle);
1458 if( pszAbstract )
1459 poLayer->SetMetadataItem("ABSTRACT", pszAbstract);
1460 CPLXMLNode* psKeywords = CPLGetXMLNode(psChildIter, "Keywords");
1461 if( psKeywords )
1462 {
1463 int nKeywordCounter = 1;
1464 for( CPLXMLNode* psKeyword = psKeywords->psChild;
1465 psKeyword != nullptr; psKeyword = psKeyword->psNext )
1466 {
1467 if( psKeyword->eType == CXT_Element && psKeyword->psChild != nullptr )
1468 {
1469 poLayer->SetMetadataItem(CPLSPrintf("KEYWORD_%d", nKeywordCounter),
1470 psKeyword->psChild->pszValue);
1471 nKeywordCounter ++;
1472 }
1473 else if( psKeyword->eType == CXT_Text )
1474 {
1475 poLayer->SetMetadataItem("KEYWORDS",
1476 psKeyword->pszValue);
1477 }
1478 }
1479 }
1480
1481 if (poSRS)
1482 {
1483 char* pszProj4 = nullptr;
1484 if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1485 {
1486 /* See http://trac.osgeo.org/gdal/ticket/4041 */
1487 const bool bTrustBounds =
1488 CPLFetchBool(
1489 papszOpenOptionsIn,
1490 "TRUST_CAPABILITIES_BOUNDS",
1491 CPLTestBool(CPLGetConfigOption(
1492 "OGR_WFS_TRUST_CAPABILITIES_BOUNDS",
1493 "FALSE")));
1494
1495 if (((bTrustBounds || (dfMinX == -180 && dfMinY == -90 && dfMaxX == 180 && dfMaxY == 90)) &&
1496 strcmp(pszProj4, "+proj=longlat +datum=WGS84 +no_defs") == 0) ||
1497 strcmp(pszDefaultSRS, "urn:ogc:def:crs:OGC:1.3:CRS84") == 0)
1498 {
1499 poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
1500 }
1501
1502 else if( bTrustBounds )
1503 {
1504 OGRSpatialReference oWGS84;
1505 oWGS84.SetWellKnownGeogCS("WGS84");
1506 oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1507 CPLPushErrorHandler(CPLQuietErrorHandler);
1508 OGRCoordinateTransformation* poCT =
1509 OGRCreateCoordinateTransformation(&oWGS84,
1510 poSRS);
1511 if( poCT )
1512 {
1513 double dfULX = dfMinX;
1514 double dfULY = dfMaxY;
1515 double dfURX = dfMaxX;
1516 double dfURY = dfMaxY;
1517 double dfLLX = dfMinX;
1518 double dfLLY = dfMinY;
1519 double dfLRX = dfMaxX;
1520 double dfLRY = dfMinY;
1521 if (poCT->Transform(1, &dfULX, &dfULY, nullptr) &&
1522 poCT->Transform(1, &dfURX, &dfURY, nullptr) &&
1523 poCT->Transform(1, &dfLLX, &dfLLY, nullptr) &&
1524 poCT->Transform(1, &dfLRX, &dfLRY, nullptr))
1525 {
1526 dfMinX = dfULX;
1527 dfMinX = std::min(dfMinX, dfURX);
1528 dfMinX = std::min(dfMinX, dfLLX);
1529 dfMinX = std::min(dfMinX, dfLRX);
1530
1531 dfMinY = dfULY;
1532 dfMinY = std::min(dfMinY, dfURY);
1533 dfMinY = std::min(dfMinY, dfLLY);
1534 dfMinY = std::min(dfMinY, dfLRY);
1535
1536 dfMaxX = dfULX;
1537 dfMaxX = std::max(dfMaxX, dfURX);
1538 dfMaxX = std::max(dfMaxX, dfLLX);
1539 dfMaxX = std::max(dfMaxX, dfLRX);
1540
1541 dfMaxY = dfULY;
1542 dfMaxY = std::max(dfMaxY, dfURY);
1543 dfMaxY = std::max(dfMaxY, dfLLY);
1544 dfMaxY = std::max(dfMaxY, dfLRY);
1545
1546 poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
1547 }
1548 }
1549 delete poCT;
1550 CPLPopErrorHandler();
1551 CPLErrorReset();
1552 }
1553 }
1554 CPLFree(pszProj4);
1555 }
1556
1557 papoLayers = (OGRWFSLayer **)CPLRealloc(papoLayers,
1558 sizeof(OGRWFSLayer*) * (nLayers + 1));
1559 papoLayers[nLayers ++] = poLayer;
1560
1561 if (psFileXML != nullptr)
1562 {
1563 CPLXMLNode* psIter = psXML->psChild;
1564 while(psIter)
1565 {
1566 if (psIter->eType == CXT_Element &&
1567 psIter->psChild &&
1568 EQUAL(psIter->pszValue, "OGRWFSLayer") &&
1569 strcmp(CPLGetXMLValue(psIter, "name", ""), l_pszName) == 0)
1570 {
1571 CPLXMLNode* psSchema = WFSFindNode( psIter->psChild, "schema" );
1572 if (psSchema)
1573 {
1574 OGRFeatureDefn* poSrcFDefn = poLayer->ParseSchema(psSchema);
1575 if (poSrcFDefn)
1576 poLayer->BuildLayerDefn(poSrcFDefn);
1577 }
1578 break;
1579 }
1580 psIter = psIter->psNext;
1581 }
1582 }
1583 }
1584 }
1585 }
1586
1587 CSLDestroy(papszTypenames);
1588
1589 if (!psFileXML) CPLDestroyXMLNode( psXML );
1590 CPLDestroyXMLNode( psStrippedXML );
1591
1592 return TRUE;
1593 }
1594
1595 /************************************************************************/
1596 /* LoadMultipleLayerDefn() */
1597 /************************************************************************/
1598
1599 /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */
1600 #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1
1601
LoadMultipleLayerDefn(const char * pszLayerName,char * pszNS,char * pszNSVal)1602 void OGRWFSDataSource::LoadMultipleLayerDefn(const char* pszLayerName,
1603 char* pszNS, char* pszNSVal)
1604 {
1605 if( !bLoadMultipleLayerDefn )
1606 return;
1607
1608 if (aoSetAlreadyTriedLayers.find(pszLayerName) != aoSetAlreadyTriedLayers.end())
1609 return;
1610
1611 char* pszPrefix = CPLStrdup(pszLayerName);
1612 char* pszColumn = strchr(pszPrefix, ':');
1613 if (pszColumn)
1614 *pszColumn = 0;
1615 else
1616 *pszPrefix = 0;
1617
1618 OGRWFSLayer* poRefLayer = dynamic_cast<OGRWFSLayer*>(GetLayerByName(pszLayerName));
1619 if (poRefLayer == nullptr)
1620 return;
1621
1622 const char* pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1623
1624 #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1625 CPLString osLayerToFetch(pszLayerName);
1626 #else
1627 CPLString osTypeNameToPost;
1628 osTypeNameToPost += " <TypeName>";
1629 osTypeNameToPost += pszLayerName;
1630 osTypeNameToPost += "</TypeName>\n";
1631 #endif
1632
1633 int nLayersToFetch = 1;
1634 aoSetAlreadyTriedLayers.insert(pszLayerName);
1635
1636 for(int i=0;i<nLayers;i++)
1637 {
1638 if (!papoLayers[i]->HasLayerDefn())
1639 {
1640 /* We must be careful to requests only layers with the same prefix/namespace */
1641 const char* l_pszName = papoLayers[i]->GetName();
1642 if (((pszPrefix[0] == 0 && strchr(l_pszName, ':') == nullptr) ||
1643 (pszPrefix[0] != 0 && strncmp(l_pszName, pszPrefix, strlen(pszPrefix)) == 0 &&
1644 l_pszName[strlen(pszPrefix)] == ':')) &&
1645 ((pszRequiredOutputFormat == nullptr && papoLayers[i]->GetRequiredOutputFormat() == nullptr) ||
1646 (pszRequiredOutputFormat != nullptr && papoLayers[i]->GetRequiredOutputFormat() != nullptr &&
1647 strcmp(pszRequiredOutputFormat, papoLayers[i]->GetRequiredOutputFormat()) == 0)))
1648 {
1649 if (aoSetAlreadyTriedLayers.find(l_pszName) != aoSetAlreadyTriedLayers.end())
1650 continue;
1651 aoSetAlreadyTriedLayers.insert(l_pszName);
1652
1653 #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1654 if (nLayersToFetch > 0)
1655 osLayerToFetch += ",";
1656 osLayerToFetch += papoLayers[i]->GetName();
1657 #else
1658 osTypeNameToPost += " <TypeName>";
1659 osTypeNameToPost += l_pszName;
1660 osTypeNameToPost += "</TypeName>\n";
1661 #endif
1662 nLayersToFetch ++;
1663
1664 /* Avoid fetching to many layer definition at a time */
1665 if (nLayersToFetch >= 50)
1666 break;
1667 }
1668 }
1669 }
1670
1671 CPLFree(pszPrefix);
1672 pszPrefix = nullptr;
1673
1674 #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1675 CPLString osURL(osBaseURL);
1676 osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
1677 osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion());
1678 osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
1679 osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch));
1680 osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
1681 osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
1682 osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
1683 osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", pszRequiredOutputFormat ? WFS_EscapeURL(pszRequiredOutputFormat).c_str() : nullptr);
1684
1685 if (pszNS && GetNeedNAMESPACE())
1686 {
1687 /* Older Deegree version require NAMESPACE */
1688 /* This has been now corrected */
1689 CPLString osValue("xmlns(");
1690 osValue += pszNS;
1691 osValue += "=";
1692 osValue += pszNSVal;
1693 osValue += ")";
1694 osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
1695 }
1696
1697 CPLHTTPResult* psResult = HTTPFetch( osURL, nullptr);
1698 #else
1699 CPLString osPost;
1700 osPost += "<?xml version=\"1.0\"?>\n";
1701 osPost += "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
1702 osPost += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
1703 osPost += " service=\"WFS\" version=\""; osPost += GetVersion(); osPost += "\"\n";
1704 osPost += " xmlns:gml=\"http://www.opengis.net/gml\"\n";
1705 osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
1706 if (pszNS && pszNSVal)
1707 {
1708 osPost += " xmlns:";
1709 osPost += pszNS;
1710 osPost += "=\"";
1711 osPost += pszNSVal;
1712 osPost += "\"\n";
1713 }
1714 osPost += " xsi:schemaLocation=\"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/";
1715 osPost += GetVersion();
1716 osPost += "/wfs.xsd\"";
1717 const char* pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1718 if (pszRequiredOutputFormat)
1719 {
1720 osPost += "\n";
1721 osPost += " outputFormat=\"";
1722 osPost += pszRequiredOutputFormat;
1723 osPost += "\"";
1724 }
1725 osPost += ">\n";
1726 osPost += osTypeNameToPost;
1727 osPost += "</wfs:DescribeFeatureType>\n";
1728
1729 //CPLDebug("WFS", "%s", osPost.c_str());
1730
1731 char** papszOptions = NULL;
1732 papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
1733 papszOptions = CSLAddNameValue(papszOptions, "HEADERS",
1734 "Content-Type: application/xml; charset=UTF-8");
1735
1736 CPLHTTPResult* psResult = HTTPFetch(GetPostTransactionURL(), papszOptions);
1737 CSLDestroy(papszOptions);
1738 #endif
1739
1740 if (psResult == nullptr)
1741 {
1742 bLoadMultipleLayerDefn = false;
1743 return;
1744 }
1745
1746 if (strstr((const char*)psResult->pabyData, "<ServiceExceptionReport") != nullptr)
1747 {
1748 if( IsOldDeegree((const char*)psResult->pabyData) )
1749 {
1750 /* just silently forgive */
1751 }
1752 else
1753 {
1754 CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
1755 psResult->pabyData);
1756 }
1757 CPLHTTPDestroyResult(psResult);
1758 bLoadMultipleLayerDefn = false;
1759 return;
1760 }
1761
1762 CPLXMLNode* psXML = CPLParseXMLString( (const char*) psResult->pabyData );
1763 if (psXML == nullptr)
1764 {
1765 CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
1766 psResult->pabyData);
1767 CPLHTTPDestroyResult(psResult);
1768 bLoadMultipleLayerDefn = false;
1769 return;
1770 }
1771 CPLHTTPDestroyResult(psResult);
1772
1773 CPLXMLNode* psSchema = WFSFindNode(psXML, "schema");
1774 if (psSchema == nullptr)
1775 {
1776 CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
1777 CPLDestroyXMLNode( psXML );
1778 bLoadMultipleLayerDefn = false;
1779 return;
1780 }
1781
1782 CPLString osTmpFileName;
1783
1784 osTmpFileName = CPLSPrintf("/vsimem/tempwfs_%p/file.xsd", this);
1785 CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
1786
1787 std::vector<GMLFeatureClass*> aosClasses;
1788 bool bFullyUnderstood = false;
1789 GMLParseXSD( osTmpFileName, aosClasses, bFullyUnderstood );
1790
1791 int nLayersFound = 0;
1792 if (!(int)aosClasses.empty())
1793 {
1794 std::vector<GMLFeatureClass*>::const_iterator oIter = aosClasses.begin();
1795 std::vector<GMLFeatureClass*>::const_iterator oEndIter = aosClasses.end();
1796 while (oIter != oEndIter)
1797 {
1798 GMLFeatureClass* poClass = *oIter;
1799 ++oIter;
1800
1801 OGRWFSLayer* poLayer = nullptr;
1802
1803 if( bKeepLayerNamePrefix && pszNS != nullptr &&
1804 strchr(poClass->GetName(), ':') == nullptr )
1805 {
1806 CPLString osWithPrefix(pszNS);
1807 osWithPrefix += ":";
1808 osWithPrefix += poClass->GetName();
1809 poLayer = (OGRWFSLayer* )GetLayerByName(osWithPrefix);
1810 }
1811 else
1812 poLayer = (OGRWFSLayer* )GetLayerByName(poClass->GetName());
1813
1814 if (poLayer)
1815 {
1816 if (!poLayer->HasLayerDefn())
1817 {
1818 nLayersFound ++;
1819
1820 CPLXMLNode* psSchemaForLayer = CPLCloneXMLTree(psSchema);
1821 CPLStripXMLNamespace( psSchemaForLayer, nullptr, TRUE );
1822 CPLXMLNode* psIter = psSchemaForLayer->psChild;
1823 bool bHasAlreadyImportedGML = false;
1824 bool bFoundComplexType = false;
1825 bool bFoundElement = false;
1826 while(psIter != nullptr)
1827 {
1828 CPLXMLNode* psIterNext = psIter->psNext;
1829 if (psIter->eType == CXT_Element &&
1830 strcmp(psIter->pszValue,"complexType") == 0)
1831 {
1832 const char* l_pszName = CPLGetXMLValue(psIter, "name", "");
1833 CPLString osExpectedName(poLayer->GetShortName());
1834 osExpectedName += "Type";
1835 CPLString osExpectedName2(poLayer->GetShortName());
1836 osExpectedName2 += "_Type";
1837 if (strcmp(l_pszName, osExpectedName) == 0 ||
1838 strcmp(l_pszName, osExpectedName2) == 0 ||
1839 strcmp(l_pszName, poLayer->GetShortName()) == 0)
1840 {
1841 bFoundComplexType = true;
1842 }
1843 else
1844 {
1845 CPLRemoveXMLChild( psSchemaForLayer, psIter );
1846 CPLDestroyXMLNode(psIter);
1847 }
1848 }
1849 else if (psIter->eType == CXT_Element &&
1850 strcmp(psIter->pszValue,"element") == 0)
1851 {
1852 const char* l_pszName = CPLGetXMLValue(psIter, "name", "");
1853 CPLString osExpectedName(poLayer->GetShortName());
1854 osExpectedName += "Type";
1855 CPLString osExpectedName2(poLayer->GetShortName());
1856 osExpectedName2 += "_Type";
1857
1858 const char* pszType = CPLGetXMLValue(psIter, "type", "");
1859 CPLString osExpectedType(poLayer->GetName());
1860 osExpectedType += "Type";
1861 CPLString osExpectedType2(poLayer->GetName());
1862 osExpectedType2 += "_Type";
1863 if (strcmp(pszType, osExpectedType) == 0 ||
1864 strcmp(pszType, osExpectedType2) == 0 ||
1865 strcmp(pszType, poLayer->GetName()) == 0 ||
1866 (strchr(pszType, ':') &&
1867 (strcmp(strchr(pszType, ':') + 1, osExpectedType) == 0 ||
1868 strcmp(strchr(pszType, ':') + 1, osExpectedType2) == 0)))
1869 {
1870 bFoundElement = true;
1871 }
1872 else if (*pszType == '\0' &&
1873 CPLGetXMLNode(psIter, "complexType") != nullptr &&
1874 (strcmp(l_pszName, osExpectedName) == 0 ||
1875 strcmp(l_pszName, osExpectedName2) == 0 ||
1876 strcmp(l_pszName, poLayer->GetShortName()) == 0) )
1877 {
1878 bFoundElement = true;
1879 bFoundComplexType = true;
1880 }
1881 else
1882 {
1883 CPLRemoveXMLChild( psSchemaForLayer, psIter );
1884 CPLDestroyXMLNode(psIter);
1885 }
1886 }
1887 else if (psIter->eType == CXT_Element &&
1888 strcmp(psIter->pszValue,"import") == 0 &&
1889 strcmp(CPLGetXMLValue(psIter, "namespace", ""),
1890 "http://www.opengis.net/gml") == 0)
1891 {
1892 if( bHasAlreadyImportedGML )
1893 {
1894 CPLRemoveXMLChild( psSchemaForLayer, psIter );
1895 CPLDestroyXMLNode(psIter);
1896 }
1897 else
1898 {
1899 bHasAlreadyImportedGML = true;
1900 }
1901 }
1902 psIter = psIterNext;
1903 }
1904
1905 if( bFoundComplexType && bFoundElement )
1906 {
1907 OGRFeatureDefn* poSrcFDefn
1908 = poLayer->ParseSchema(psSchemaForLayer);
1909 if (poSrcFDefn)
1910 {
1911 poLayer->BuildLayerDefn(poSrcFDefn);
1912 SaveLayerSchema(poLayer->GetName(),
1913 psSchemaForLayer);
1914 }
1915 }
1916
1917 CPLDestroyXMLNode(psSchemaForLayer);
1918 }
1919 else
1920 {
1921 CPLDebug( "WFS",
1922 "Found several time schema for layer %s in "
1923 "server response. Should not happen",
1924 poClass->GetName());
1925 }
1926 }
1927 delete poClass;
1928 }
1929 }
1930
1931 if (nLayersFound != nLayersToFetch)
1932 {
1933 CPLDebug( "WFS",
1934 "Turn off loading of multiple layer definitions at a "
1935 "single time");
1936 bLoadMultipleLayerDefn = false;
1937 }
1938
1939 VSIUnlink(osTmpFileName);
1940
1941 CPLDestroyXMLNode( psXML );
1942 }
1943
1944 /************************************************************************/
1945 /* SaveLayerSchema() */
1946 /************************************************************************/
1947
SaveLayerSchema(const char * pszLayerName,CPLXMLNode * psSchema)1948 void OGRWFSDataSource::SaveLayerSchema( const char* pszLayerName,
1949 CPLXMLNode* psSchema )
1950 {
1951 if (psFileXML != nullptr)
1952 {
1953 bRewriteFile = true;
1954 CPLXMLNode* psLayerNode =
1955 CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer");
1956 CPLSetXMLValue(psLayerNode, "#name", pszLayerName);
1957 CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema));
1958 CPLAddXMLChild(psFileXML, psLayerNode);
1959 }
1960 }
1961
1962 /************************************************************************/
1963 /* IsOldDeegree() */
1964 /************************************************************************/
1965
IsOldDeegree(const char * pszErrorString)1966 bool OGRWFSDataSource::IsOldDeegree(const char* pszErrorString)
1967 {
1968 if( !bNeedNAMESPACE &&
1969 strstr(pszErrorString,
1970 "Invalid \"TYPENAME\" parameter. "
1971 "No binding for prefix") != nullptr )
1972 {
1973 bNeedNAMESPACE = true;
1974 return true;
1975 }
1976 return false;
1977 }
1978
1979 /************************************************************************/
1980 /* WFS_EscapeURL() */
1981 /************************************************************************/
1982
WFS_EscapeURL(const char * pszURL)1983 CPLString WFS_EscapeURL(const char* pszURL)
1984 {
1985 CPLString osEscapedURL;
1986
1987 /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */
1988 /* colon (:) or comma (,). Causes problems with servers such as http://www.mapinfo.com/miwfs? */
1989
1990 for( int i = 0; pszURL[i] != '\0' ; i++ )
1991 {
1992 char ch = pszURL[i];
1993 if( (ch >= 'a' && ch <= 'z')
1994 || (ch >= 'A' && ch <= 'Z')
1995 || (ch >= '0' && ch <= '9')
1996 || ch == '_' || ch == '.'
1997 || ch == ':' || ch == ',' )
1998 {
1999 osEscapedURL += ch;
2000 }
2001 else
2002 {
2003 char szPercentEncoded[10];
2004 snprintf( szPercentEncoded, sizeof(szPercentEncoded),
2005 "%%%02X", ((unsigned char*)pszURL)[i] );
2006 osEscapedURL += szPercentEncoded;
2007 }
2008 }
2009
2010 return osEscapedURL;
2011 }
2012
2013 /************************************************************************/
2014 /* WFS_DecodeURL() */
2015 /************************************************************************/
2016
WFS_DecodeURL(const CPLString & osSrc)2017 CPLString WFS_DecodeURL(const CPLString &osSrc)
2018 {
2019 CPLString ret;
2020 for( size_t i=0; i<osSrc.length(); i++ )
2021 {
2022 if (osSrc[i]=='%' && i+2 < osSrc.length())
2023 {
2024 unsigned int ii = 0;
2025 sscanf(osSrc.substr(i+1,2).c_str(), "%x", &ii);
2026 char ch = static_cast<char>(ii);
2027 ret += ch;
2028 i = i + 2;
2029 }
2030 else
2031 {
2032 ret+=osSrc[i];
2033 }
2034 }
2035 return ret;
2036 }
2037
2038 /************************************************************************/
2039 /* HTTPFetch() */
2040 /************************************************************************/
2041
HTTPFetch(const char * pszURL,char ** papszOptions)2042 CPLHTTPResult* OGRWFSDataSource::HTTPFetch( const char* pszURL, char** papszOptions )
2043 {
2044 char** papszNewOptions = CSLDuplicate(papszOptions);
2045 if( bUseHttp10 )
2046 papszNewOptions = CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0");
2047 if (papszHttpOptions)
2048 papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions);
2049 CPLHTTPResult* psResult = CPLHTTPFetch( pszURL, papszNewOptions );
2050 CSLDestroy(papszNewOptions);
2051
2052 if (psResult == nullptr)
2053 {
2054 return nullptr;
2055 }
2056 if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
2057 {
2058 // A few buggy servers return chunked data with erroneous
2059 // remaining bytes value curl does not like this. Retry with
2060 // HTTP 1.0 protocol instead that does not support chunked
2061 // data.
2062 if( psResult->pszErrBuf &&
2063 strstr(psResult->pszErrBuf,
2064 "transfer closed with outstanding read data remaining") &&
2065 !bUseHttp10 )
2066 {
2067 CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP 1.0 protocol");
2068 bUseHttp10 = true;
2069 CPLHTTPDestroyResult(psResult);
2070 return HTTPFetch(pszURL, papszOptions);
2071 }
2072
2073 CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s (%d)",
2074 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown", psResult->nStatus);
2075 CPLHTTPDestroyResult(psResult);
2076 return nullptr;
2077 }
2078 if (psResult->pabyData == nullptr)
2079 {
2080 CPLError(CE_Failure, CPLE_AppDefined, "Empty content returned by server");
2081 CPLHTTPDestroyResult(psResult);
2082 return nullptr;
2083 }
2084 return psResult;
2085 }
2086
2087 /************************************************************************/
2088 /* ExecuteSQL() */
2089 /************************************************************************/
2090
ExecuteSQL(const char * pszSQLCommand,OGRGeometry * poSpatialFilter,const char * pszDialect)2091 OGRLayer * OGRWFSDataSource::ExecuteSQL( const char *pszSQLCommand,
2092 OGRGeometry *poSpatialFilter,
2093 const char *pszDialect )
2094
2095 {
2096 swq_select_parse_options oParseOptions;
2097 oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar();
2098
2099 /* -------------------------------------------------------------------- */
2100 /* Use generic implementation for recognized dialects */
2101 /* -------------------------------------------------------------------- */
2102 if( IsGenericSQLDialect(pszDialect) )
2103 {
2104 OGRLayer* poResLayer = GDALDataset::ExecuteSQL( pszSQLCommand,
2105 poSpatialFilter,
2106 pszDialect,
2107 &oParseOptions );
2108 oMap[poResLayer] = nullptr;
2109 return poResLayer;
2110 }
2111
2112 /* -------------------------------------------------------------------- */
2113 /* Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */
2114 /* -------------------------------------------------------------------- */
2115 if( STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM ") )
2116 {
2117 const char* pszIter = pszSQLCommand + 33;
2118 while(*pszIter && *pszIter != ' ')
2119 pszIter ++;
2120
2121 CPLString osName = pszSQLCommand + 33;
2122 osName.resize(pszIter - (pszSQLCommand + 33));
2123 OGRWFSLayer* poLayer = (OGRWFSLayer*)GetLayerByName(osName);
2124 if (poLayer == nullptr)
2125 {
2126 CPLError(CE_Failure, CPLE_AppDefined,
2127 "Unknown layer : %s", osName.c_str());
2128 return nullptr;
2129 }
2130
2131 GDALDriver* poMEMDrv = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName("Memory");
2132 if (poMEMDrv == nullptr)
2133 {
2134 CPLError(CE_Failure, CPLE_AppDefined, "Cannot load 'Memory' driver");
2135 return nullptr;
2136 }
2137
2138 GDALDataset* poMEMDS = poMEMDrv->Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr);
2139 OGRLayer* poMEMLayer = poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr);
2140 OGRFieldDefn oFDefn("gml_id", OFTString);
2141 poMEMLayer->CreateField(&oFDefn);
2142
2143 const std::vector<CPLString>& aosFIDList = poLayer->GetLastInsertedFIDList();
2144 std::vector<CPLString>::const_iterator oIter = aosFIDList.begin();
2145 std::vector<CPLString>::const_iterator oEndIter = aosFIDList.end();
2146 while (oIter != oEndIter)
2147 {
2148 const CPLString& osFID = *oIter;
2149 OGRFeature* poFeature = new OGRFeature(poMEMLayer->GetLayerDefn());
2150 poFeature->SetField(0, osFID);
2151 CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(poFeature));
2152 delete poFeature;
2153 ++oIter;
2154 }
2155
2156 OGRLayer* poResLayer = new OGRWFSWrappedResultLayer(poMEMDS, poMEMLayer);
2157 oMap[poResLayer] = nullptr;
2158 return poResLayer;
2159 }
2160
2161 /* -------------------------------------------------------------------- */
2162 /* Deal with "DELETE FROM layer_name WHERE expression" statement */
2163 /* -------------------------------------------------------------------- */
2164 if( STARTS_WITH_CI(pszSQLCommand, "DELETE FROM ") )
2165 {
2166 const char* pszIter = pszSQLCommand + 12;
2167 while(*pszIter && *pszIter != ' ')
2168 pszIter ++;
2169 if (*pszIter == 0)
2170 {
2171 CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement");
2172 return nullptr;
2173 }
2174
2175 CPLString osName = pszSQLCommand + 12;
2176 osName.resize(pszIter - (pszSQLCommand + 12));
2177 OGRWFSLayer* poLayer = (OGRWFSLayer*)GetLayerByName(osName);
2178 if (poLayer == nullptr)
2179 {
2180 CPLError(CE_Failure, CPLE_AppDefined,
2181 "Unknown layer : %s", osName.c_str());
2182 return nullptr;
2183 }
2184
2185 while(*pszIter == ' ')
2186 pszIter ++;
2187 if (!STARTS_WITH_CI(pszIter, "WHERE "))
2188 {
2189 CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing");
2190 return nullptr;
2191 }
2192 pszIter += 5;
2193
2194 const char* pszQuery = pszIter;
2195
2196 /* Check with the generic SQL engine that this is a valid WHERE clause */
2197 OGRFeatureQuery oQuery;
2198 OGRErr eErr = oQuery.Compile( poLayer->GetLayerDefn(), pszQuery );
2199 if( eErr != OGRERR_NONE )
2200 {
2201 return nullptr;
2202 }
2203
2204 /* Now turn this into OGC Filter language if possible */
2205 int bNeedsNullCheck = FALSE;
2206 int nVersion = (strcmp(GetVersion(),"1.0.0") == 0) ? 100 : 110;
2207 swq_expr_node* poNode = (swq_expr_node*) oQuery.GetSWQExpr();
2208 poNode->ReplaceBetweenByGEAndLERecurse();
2209 CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter(
2210 poNode,
2211 nullptr,
2212 poLayer->GetLayerDefn(),
2213 nVersion,
2214 bPropertyIsNotEqualToSupported,
2215 bUseFeatureId,
2216 bGmlObjectIdNeedsGMLPrefix,
2217 "",
2218 &bNeedsNullCheck);
2219 if (bNeedsNullCheck && !HasNullCheck())
2220 osOGCFilter = "";
2221
2222 if (osOGCFilter.empty())
2223 {
2224 CPLError(CE_Failure, CPLE_AppDefined, "Cannot convert WHERE clause into a OGC filter");
2225 return nullptr;
2226 }
2227
2228 poLayer->DeleteFromFilter(osOGCFilter);
2229
2230 return nullptr;
2231 }
2232
2233 /* -------------------------------------------------------------------- */
2234 /* Deal with "SELECT xxxx ORDER BY" statement */
2235 /* -------------------------------------------------------------------- */
2236 if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
2237 {
2238 swq_select* psSelectInfo = new swq_select();
2239 if( psSelectInfo->preparse( pszSQLCommand, TRUE ) != CE_None )
2240 {
2241 delete psSelectInfo;
2242 return nullptr;
2243 }
2244 int iLayer = 0;
2245 if( strcmp(GetVersion(),"1.0.0") != 0 &&
2246 psSelectInfo->table_count == 1 &&
2247 psSelectInfo->table_defs[0].data_source == nullptr &&
2248 (iLayer = GetLayerIndex( psSelectInfo->table_defs[0].table_name )) >= 0 &&
2249 psSelectInfo->join_count == 0 &&
2250 psSelectInfo->order_specs > 0 &&
2251 psSelectInfo->poOtherSelect == nullptr )
2252 {
2253 OGRWFSLayer* poSrcLayer = papoLayers[iLayer];
2254 std::vector<OGRWFSSortDesc> aoSortColumns;
2255 int i = 0; // Used after for.
2256 for( ; i < psSelectInfo->order_specs; i++ )
2257 {
2258 int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
2259 psSelectInfo->order_defs[i].field_name);
2260 if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0)
2261 break;
2262
2263 /* Make sure to have the right case */
2264 const char* pszFieldName = poSrcLayer->GetLayerDefn()->
2265 GetFieldDefn(nFieldIndex)->GetNameRef();
2266
2267 OGRWFSSortDesc oSortDesc(pszFieldName,
2268 psSelectInfo->order_defs[i].ascending_flag);
2269 aoSortColumns.push_back(oSortDesc);
2270 }
2271
2272 if( i == psSelectInfo->order_specs )
2273 {
2274 OGRWFSLayer* poDupLayer = poSrcLayer->Clone();
2275
2276 poDupLayer->SetOrderBy(aoSortColumns);
2277 int nBackup = psSelectInfo->order_specs;
2278 psSelectInfo->order_specs = 0;
2279 char* pszSQLWithoutOrderBy = psSelectInfo->Unparse();
2280 CPLDebug("WFS", "SQL without ORDER BY: %s", pszSQLWithoutOrderBy);
2281 psSelectInfo->order_specs = nBackup;
2282 delete psSelectInfo;
2283 psSelectInfo = nullptr;
2284
2285 /* Just set poDupLayer in the papoLayers for the time of the */
2286 /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer references */
2287 /* that temporary layer */
2288 papoLayers[iLayer] = poDupLayer;
2289
2290 OGRLayer* poResLayer = GDALDataset::ExecuteSQL( pszSQLWithoutOrderBy,
2291 poSpatialFilter,
2292 pszDialect,
2293 &oParseOptions );
2294 papoLayers[iLayer] = poSrcLayer;
2295
2296 CPLFree(pszSQLWithoutOrderBy);
2297
2298 if (poResLayer != nullptr)
2299 oMap[poResLayer] = poDupLayer;
2300 else
2301 delete poDupLayer;
2302 return poResLayer;
2303 }
2304 }
2305 else if( bStandardJoinsWFS2 &&
2306 psSelectInfo->join_count > 0 &&
2307 psSelectInfo->poOtherSelect == nullptr )
2308 {
2309 // Just to make sure everything is valid, but we won't use
2310 // that one as we want to run the join on server-side
2311 oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE;
2312 oParseOptions.bAddSecondaryTablesGeometryFields = TRUE;
2313 oParseOptions.bAlwaysPrefixWithTableName = TRUE;
2314 oParseOptions.bAllowDistinctOnGeometryField = TRUE;
2315 oParseOptions.bAllowDistinctOnMultipleFields = TRUE;
2316 GDALSQLParseInfo* psParseInfo = BuildParseInfo(psSelectInfo,
2317 &oParseOptions);
2318 oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE;
2319 oParseOptions.bAddSecondaryTablesGeometryFields = FALSE;
2320 oParseOptions.bAlwaysPrefixWithTableName = FALSE;
2321 oParseOptions.bAllowDistinctOnGeometryField = FALSE;
2322 oParseOptions.bAllowDistinctOnMultipleFields = FALSE;
2323 const bool bOK = psParseInfo != nullptr;
2324 DestroyParseInfo(psParseInfo);
2325
2326 OGRLayer* poResLayer = nullptr;
2327 if( bOK )
2328 {
2329 poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo);
2330 oMap[poResLayer] = nullptr;
2331 }
2332
2333 delete psSelectInfo;
2334 return poResLayer;
2335 }
2336
2337 delete psSelectInfo;
2338 }
2339
2340 OGRLayer* poResLayer = OGRDataSource::ExecuteSQL( pszSQLCommand,
2341 poSpatialFilter,
2342 pszDialect,
2343 &oParseOptions );
2344 oMap[poResLayer] = nullptr;
2345 return poResLayer;
2346 }
2347
2348 /************************************************************************/
2349 /* ReleaseResultSet() */
2350 /************************************************************************/
2351
ReleaseResultSet(OGRLayer * poResultsSet)2352 void OGRWFSDataSource::ReleaseResultSet( OGRLayer * poResultsSet )
2353 {
2354 if (poResultsSet == nullptr)
2355 return;
2356
2357 std::map<OGRLayer*, OGRLayer*>::iterator oIter = oMap.find(poResultsSet);
2358 if (oIter != oMap.end())
2359 {
2360 /* Destroy first the result layer, because it still references */
2361 /* the poDupLayer (oIter->second) */
2362 delete poResultsSet;
2363
2364 delete oIter->second;
2365 oMap.erase(oIter);
2366 }
2367 else
2368 {
2369 CPLError(CE_Failure, CPLE_AppDefined, "Trying to destroy an invalid result set !");
2370 }
2371 }
2372