LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/wfs - ogrwfslayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1228 1499 81.9 %
Date: 2026-04-15 22:10:00 Functions: 39 42 92.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WFS Translator
       4             :  * Purpose:  Implements OGRWFSLayer 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             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "ogr_wfs.h"
      15             : #include "ogr_api.h"
      16             : #include "cpl_minixml.h"
      17             : #include "cpl_http.h"
      18             : #include "parsexsd.h"
      19             : #include "ogrwfsfilter.h"
      20             : 
      21             : /************************************************************************/
      22             : /*                            OGRWFSLayer()                             */
      23             : /************************************************************************/
      24             : 
      25         213 : OGRWFSLayer::OGRWFSLayer(OGRWFSDataSource *poDSIn, OGRSpatialReference *poSRSIn,
      26             :                          int bAxisOrderAlreadyInvertedIn,
      27             :                          const char *pszBaseURLIn, const char *pszNameIn,
      28         213 :                          const char *pszNSIn, const char *pszNSValIn)
      29             :     : poDS(poDSIn), bAxisOrderAlreadyInverted(bAxisOrderAlreadyInvertedIn),
      30         213 :       m_poSRS(poSRSIn), pszBaseURL(CPLStrdup(pszBaseURLIn)),
      31         426 :       pszName(CPLStrdup(pszNameIn)),
      32         213 :       pszNS(pszNSIn ? CPLStrdup(pszNSIn) : nullptr),
      33         213 :       pszNSVal(pszNSValIn ? CPLStrdup(pszNSValIn) : nullptr),
      34             :       // If changing that, change in the GML driver too
      35         852 :       m_osTmpDir(VSIMemGenerateHiddenFilename("_ogr_wfs_"))
      36             : {
      37         213 :     SetDescription(pszName);
      38         213 : }
      39             : 
      40             : /************************************************************************/
      41             : /*                               Clone()                                */
      42             : /************************************************************************/
      43             : 
      44           0 : OGRWFSLayer *OGRWFSLayer::Clone()
      45             : {
      46             :     OGRWFSLayer *poDupLayer =
      47           0 :         new OGRWFSLayer(poDS, m_poSRS, bAxisOrderAlreadyInverted, pszBaseURL,
      48           0 :                         pszName, pszNS, pszNSVal);
      49           0 :     if (m_poSRS)
      50           0 :         m_poSRS->Reference();
      51           0 :     poDupLayer->poFeatureDefn = GetLayerDefn()->Clone();
      52           0 :     poDupLayer->poFeatureDefn->Reference();
      53           0 :     poDupLayer->bGotApproximateLayerDefn = bGotApproximateLayerDefn;
      54           0 :     poDupLayer->eGeomType = poDupLayer->poFeatureDefn->GetGeomType();
      55           0 :     poDupLayer->pszRequiredOutputFormat =
      56           0 :         pszRequiredOutputFormat ? CPLStrdup(pszRequiredOutputFormat) : nullptr;
      57             : 
      58             :     /* Copy existing schema file if already found */
      59           0 :     CPLString osSrcFileName = CPLSPrintf("%s/file.xsd", m_osTmpDir.c_str());
      60             :     CPLString osTargetFileName =
      61           0 :         CPLSPrintf("%s/file.xsd", poDupLayer->m_osTmpDir.c_str());
      62           0 :     CPL_IGNORE_RET_VAL(CPLCopyFile(osTargetFileName, osSrcFileName));
      63             : 
      64           0 :     return poDupLayer;
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*                            ~OGRWFSLayer()                            */
      69             : /************************************************************************/
      70             : 
      71         426 : OGRWFSLayer::~OGRWFSLayer()
      72             : 
      73             : {
      74         213 :     if (bInTransaction)
      75           0 :         OGRWFSLayer::CommitTransaction();
      76             : 
      77         213 :     if (m_poSRS != nullptr)
      78         194 :         m_poSRS->Release();
      79             : 
      80         213 :     if (poFeatureDefn != nullptr)
      81         167 :         poFeatureDefn->Release();
      82         213 :     delete poGMLFeatureClass;
      83             : 
      84         213 :     CPLFree(pszBaseURL);
      85         213 :     CPLFree(pszName);
      86         213 :     CPLFree(pszNS);
      87         213 :     CPLFree(pszNSVal);
      88             : 
      89         213 :     GDALClose(poBaseDS);
      90             : 
      91         213 :     delete poFetchedFilterGeom;
      92             : 
      93         213 :     VSIRmdirRecursive(m_osTmpDir.c_str());
      94             : 
      95         213 :     CPLFree(pszRequiredOutputFormat);
      96         426 : }
      97             : 
      98             : /************************************************************************/
      99             : /*                            SetActiveSRS()                            */
     100             : /************************************************************************/
     101             : 
     102           4 : OGRErr OGRWFSLayer::SetActiveSRS(int /*iGeomField*/,
     103             :                                  const OGRSpatialReference *poSRS)
     104             : {
     105           4 :     if (poSRS == nullptr)
     106           1 :         return OGRERR_FAILURE;
     107           3 :     const char *const apszOptions[] = {
     108             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
     109           3 :     size_t i = 0;
     110           9 :     for (const auto &poSupportedSRS : m_apoSupportedCRSList)
     111             :     {
     112           8 :         if (poSupportedSRS->IsSame(poSRS, apszOptions))
     113             :         {
     114           2 :             m_osSRSName = m_aosSupportedCRSList[i];
     115           2 :             if (m_poSRS)
     116           2 :                 m_poSRS->Release();
     117           2 :             m_poSRS = poSRS->Clone();
     118           2 :             m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     119           2 :             if (poFeatureDefn)
     120             :             {
     121           2 :                 auto poGeomFieldDefn = poFeatureDefn->GetGeomFieldDefn(0);
     122           2 :                 if (poGeomFieldDefn)
     123             :                 {
     124           2 :                     poGeomFieldDefn->SetSpatialRef(m_poSRS);
     125             :                 }
     126             :             }
     127           2 :             m_oExtents = OGREnvelope();
     128           2 :             if (m_oWGS84Extents.IsInit())
     129             :             {
     130           4 :                 OGRSpatialReference oWGS84;
     131           2 :                 oWGS84.SetWellKnownGeogCS("WGS84");
     132           2 :                 oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     133             :                 auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
     134           4 :                     OGRCreateCoordinateTransformation(&oWGS84, m_poSRS));
     135           2 :                 if (poCT)
     136             :                 {
     137           2 :                     poCT->TransformBounds(
     138             :                         m_oWGS84Extents.MinX, m_oWGS84Extents.MinY,
     139             :                         m_oWGS84Extents.MaxX, m_oWGS84Extents.MaxY,
     140             :                         &m_oExtents.MinX, &m_oExtents.MinY, &m_oExtents.MaxX,
     141           2 :                         &m_oExtents.MaxY, 20);
     142             :                 }
     143             :             }
     144           2 :             SetSpatialFilter(nullptr);
     145           2 :             ResetReading();
     146           2 :             return OGRERR_NONE;
     147             :         }
     148           6 :         ++i;
     149             :     }
     150           1 :     return OGRERR_FAILURE;
     151             : }
     152             : 
     153             : /************************************************************************/
     154             : /*                     GetDescribeFeatureTypeURL()                      */
     155             : /************************************************************************/
     156             : 
     157         167 : CPLString OGRWFSLayer::GetDescribeFeatureTypeURL(CPL_UNUSED int bWithNS)
     158             : {
     159         167 :     CPLString osURL(pszBaseURL);
     160         167 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
     161         167 :     osURL = CPLURLAddKVP(osURL, "VERSION", poDS->GetVersion());
     162         167 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
     163         167 :     osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(pszName));
     164         167 :     osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
     165         167 :     osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
     166         167 :     osURL = CPLURLAddKVP(osURL, "COUNT", nullptr);
     167         167 :     osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
     168         334 :     osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
     169         167 :                          pszRequiredOutputFormat &&
     170           0 :                                  !EQUAL(pszRequiredOutputFormat, "json")
     171         167 :                              ? WFS_EscapeURL(pszRequiredOutputFormat).c_str()
     172         167 :                              : nullptr);
     173             : 
     174         167 :     if (pszNS && poDS->GetNeedNAMESPACE())
     175             :     {
     176             :         /* Older Deegree version require NAMESPACE (e.g.
     177             :          * http://www.nokis.org/deegree2/ogcwebservice) */
     178             :         /* This has been now corrected */
     179           0 :         CPLString osValue("xmlns(");
     180           0 :         osValue += pszNS;
     181           0 :         osValue += "=";
     182           0 :         osValue += pszNSVal;
     183           0 :         osValue += ")";
     184           0 :         osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
     185             :     }
     186             : 
     187         167 :     return osURL;
     188             : }
     189             : 
     190             : /************************************************************************/
     191             : /*                        DescribeFeatureType()                         */
     192             : /************************************************************************/
     193             : 
     194         105 : OGRFeatureDefn *OGRWFSLayer::DescribeFeatureType()
     195             : {
     196         210 :     CPLString osURL = GetDescribeFeatureTypeURL(TRUE);
     197             : 
     198         105 :     CPLDebug("WFS", "%s", osURL.c_str());
     199             : 
     200         105 :     CPLHTTPResult *psResult = poDS->HTTPFetch(osURL, nullptr);
     201         105 :     if (psResult == nullptr)
     202             :     {
     203          15 :         return nullptr;
     204             :     }
     205             : 
     206          90 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
     207             :                "<ServiceExceptionReport") != nullptr)
     208             :     {
     209           0 :         if (poDS->IsOldDeegree(
     210           0 :                 reinterpret_cast<const char *>(psResult->pabyData)))
     211             :         {
     212           0 :             CPLHTTPDestroyResult(psResult);
     213           0 :             return DescribeFeatureType();
     214             :         }
     215           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
     216             :                  psResult->pabyData);
     217           0 :         CPLHTTPDestroyResult(psResult);
     218           0 :         return nullptr;
     219             :     }
     220             : 
     221             :     CPLXMLNode *psXML =
     222          90 :         CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
     223          90 :     if (psXML == nullptr)
     224             :     {
     225           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
     226             :                  psResult->pabyData);
     227           1 :         CPLHTTPDestroyResult(psResult);
     228           1 :         return nullptr;
     229             :     }
     230          89 :     CPLHTTPDestroyResult(psResult);
     231             : 
     232          89 :     const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema");
     233          89 :     if (psSchema == nullptr)
     234             :     {
     235           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
     236           1 :         CPLDestroyXMLNode(psXML);
     237             : 
     238           1 :         return nullptr;
     239             :     }
     240             : 
     241          88 :     OGRFeatureDefn *poFDefn = ParseSchema(psSchema);
     242          88 :     if (poFDefn)
     243          81 :         poDS->SaveLayerSchema(pszName, psSchema);
     244             : 
     245          88 :     CPLDestroyXMLNode(psXML);
     246          88 :     return poFDefn;
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                            ParseSchema()                             */
     251             : /************************************************************************/
     252             : 
     253         150 : OGRFeatureDefn *OGRWFSLayer::ParseSchema(const CPLXMLNode *psSchema)
     254             : {
     255         150 :     osTargetNamespace = CPLGetXMLValue(psSchema, "targetNamespace", "");
     256             : 
     257         300 :     CPLString osTmpFileName;
     258             : 
     259         150 :     osTmpFileName = CPLSPrintf("%s/file.xsd", m_osTmpDir.c_str());
     260         150 :     CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
     261             : 
     262         300 :     std::vector<GMLFeatureClass *> aosClasses;
     263         150 :     bool bFullyUnderstood = false;
     264         150 :     bool bUseSchemaImports = false;
     265         150 :     bool bHaveSchema = GMLParseXSD(osTmpFileName, bUseSchemaImports, aosClasses,
     266             :                                    bFullyUnderstood);
     267             : 
     268         150 :     if (bHaveSchema && aosClasses.size() == 1)
     269             :     {
     270             :         // CPLDebug("WFS", "Creating %s for %s", osTmpFileName.c_str(),
     271             :         // GetName());
     272         143 :         return BuildLayerDefnFromFeatureClass(aosClasses[0]);
     273             :     }
     274           7 :     else if (bHaveSchema)
     275             :     {
     276             :         std::vector<GMLFeatureClass *>::const_iterator oIter =
     277           0 :             aosClasses.begin();
     278             :         std::vector<GMLFeatureClass *>::const_iterator oEndIter =
     279           0 :             aosClasses.end();
     280           0 :         while (oIter != oEndIter)
     281             :         {
     282           0 :             GMLFeatureClass *poClass = *oIter;
     283           0 :             ++oIter;
     284           0 :             delete poClass;
     285             :         }
     286             :     }
     287             : 
     288           7 :     VSIUnlink(osTmpFileName);
     289             : 
     290           7 :     return nullptr;
     291             : }
     292             : 
     293             : /************************************************************************/
     294             : /*                   BuildLayerDefnFromFeatureClass()                   */
     295             : /************************************************************************/
     296             : 
     297             : OGRFeatureDefn *
     298         143 : OGRWFSLayer::BuildLayerDefnFromFeatureClass(GMLFeatureClass *poClass)
     299             : {
     300         143 :     poGMLFeatureClass = poClass;
     301             : 
     302         143 :     OGRFeatureDefn *poFDefn = new OGRFeatureDefn(pszName);
     303         143 :     poFDefn->SetGeomType(wkbNone);
     304         143 :     if (poGMLFeatureClass->GetGeometryPropertyCount() > 0)
     305             :     {
     306         128 :         poFDefn->SetGeomType(static_cast<OGRwkbGeometryType>(
     307         128 :             poGMLFeatureClass->GetGeometryProperty(0)->GetType()));
     308         128 :         poFDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
     309             :     }
     310             : 
     311             :     /* -------------------------------------------------------------------- */
     312             :     /*      Added attributes (properties).                                  */
     313             :     /* -------------------------------------------------------------------- */
     314         143 :     if (poDS->ExposeGMLId())
     315             :     {
     316         280 :         OGRFieldDefn oField("gml_id", OFTString);
     317         140 :         oField.SetNullable(FALSE);
     318         140 :         poFDefn->AddFieldDefn(&oField);
     319             :     }
     320             : 
     321         836 :     for (int iField = 0; iField < poGMLFeatureClass->GetPropertyCount();
     322             :          iField++)
     323             :     {
     324         693 :         GMLPropertyDefn *poProperty = poGMLFeatureClass->GetProperty(iField);
     325         693 :         OGRFieldSubType eSubType = OFSTNone;
     326             :         const OGRFieldType eFType =
     327         693 :             GML_GetOGRFieldType(poProperty->GetType(), eSubType);
     328             : 
     329        1386 :         OGRFieldDefn oField(poProperty->GetName(), eFType);
     330         693 :         oField.SetSubType(eSubType);
     331         693 :         if (STARTS_WITH_CI(oField.GetNameRef(), "ogr:"))
     332           0 :             oField.SetName(poProperty->GetName() + 4);
     333         693 :         if (poProperty->GetWidth() > 0)
     334           0 :             oField.SetWidth(poProperty->GetWidth());
     335         693 :         if (poProperty->GetPrecision() > 0)
     336           0 :             oField.SetPrecision(poProperty->GetPrecision());
     337         693 :         if (!poDS->IsEmptyAsNull())
     338           0 :             oField.SetNullable(poProperty->IsNullable());
     339             : 
     340         693 :         poFDefn->AddFieldDefn(&oField);
     341             :     }
     342             : 
     343         143 :     if (poGMLFeatureClass->GetGeometryPropertyCount() > 0)
     344             :     {
     345             :         const char *pszGeometryColumnName =
     346         128 :             poGMLFeatureClass->GetGeometryProperty(0)->GetSrcElement();
     347         128 :         if (pszGeometryColumnName[0] != '\0')
     348             :         {
     349         128 :             osGeometryColumnName = pszGeometryColumnName;
     350         128 :             if (poFDefn->GetGeomFieldCount() > 0)
     351             :             {
     352         256 :                 poFDefn->GetGeomFieldDefn(0)->SetNullable(
     353         128 :                     poGMLFeatureClass->GetGeometryProperty(0)->IsNullable());
     354         128 :                 poFDefn->GetGeomFieldDefn(0)->SetName(pszGeometryColumnName);
     355             :             }
     356             :         }
     357             :     }
     358             : 
     359         143 :     return poFDefn;
     360             : }
     361             : 
     362             : /************************************************************************/
     363             : /*                         MakeGetFeatureURL()                          */
     364             : /************************************************************************/
     365             : 
     366         147 : CPLString OGRWFSLayer::MakeGetFeatureURL(int nRequestMaxFeatures,
     367             :                                          int bRequestHits)
     368             : {
     369         147 :     CPLString osURL(pszBaseURL);
     370         147 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
     371         147 :     osURL = CPLURLAddKVP(osURL, "VERSION", poDS->GetVersion());
     372         147 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "GetFeature");
     373         147 :     if (atoi(poDS->GetVersion()) >= 2)
     374          36 :         osURL = CPLURLAddKVP(osURL, "TYPENAMES", WFS_EscapeURL(pszName));
     375             :     else
     376         111 :         osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(pszName));
     377         147 :     if (!m_osSRSName.empty())
     378             :         osURL =
     379           1 :             CPLURLAddKVP(osURL, "SRSNAME", WFS_EscapeURL(m_osSRSName.c_str()));
     380         147 :     if (pszRequiredOutputFormat)
     381           0 :         osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
     382           0 :                              WFS_EscapeURL(pszRequiredOutputFormat));
     383             : 
     384         147 :     if (poDS->IsPagingAllowed() && !bRequestHits)
     385             :     {
     386          20 :         nRequestMaxFeatures = poDS->GetPageSize();
     387             :         /* If the feature count is known and is less than the page size, we don't
     388             :          * need to do paging. Skipping the pagination parameters improves compatibility
     389             :          * with remote datasources that don't have a primary key.
     390             :          * Without a primary key, the WFS server can't support paging, since there
     391             :          * is no natural sort order defined. */
     392          20 :         if (nFeatures < 0 ||
     393           6 :             (nRequestMaxFeatures && nFeatures > nRequestMaxFeatures))
     394             :         {
     395             :             osURL =
     396          36 :                 CPLURLAddKVP(osURL, "STARTINDEX",
     397          18 :                              CPLSPrintf("%d", nPagingStartIndex +
     398          36 :                                                   poDS->GetBaseStartIndex()));
     399          18 :             bPagingActive = true;
     400             :         }
     401             :     }
     402             : 
     403         147 :     if (nRequestMaxFeatures)
     404             :     {
     405         132 :         osURL = CPLURLAddKVP(
     406          44 :             osURL, atoi(poDS->GetVersion()) >= 2 ? "COUNT" : "MAXFEATURES",
     407          44 :             CPLSPrintf("%d", nRequestMaxFeatures));
     408             :     }
     409         147 :     if (pszNS && poDS->GetNeedNAMESPACE())
     410             :     {
     411             :         /* Older Deegree version require NAMESPACE (e.g.
     412             :          * http://www.nokis.org/deegree2/ogcwebservice) */
     413             :         /* This has been now corrected */
     414           0 :         CPLString osValue("xmlns(");
     415           0 :         osValue += pszNS;
     416           0 :         osValue += "=";
     417           0 :         osValue += pszNSVal;
     418           0 :         osValue += ")";
     419           0 :         osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
     420             :     }
     421             : 
     422         147 :     delete poFetchedFilterGeom;
     423         147 :     poFetchedFilterGeom = nullptr;
     424             : 
     425         294 :     CPLString osGeomFilter;
     426             : 
     427         147 :     if (m_poFilterGeom != nullptr && !osGeometryColumnName.empty())
     428             :     {
     429           6 :         OGREnvelope oEnvelope;
     430           6 :         m_poFilterGeom->getEnvelope(&oEnvelope);
     431             : 
     432           6 :         poFetchedFilterGeom = m_poFilterGeom->clone();
     433             : 
     434           6 :         osGeomFilter = "<BBOX>";
     435           6 :         if (atoi(poDS->GetVersion()) >= 2)
     436           1 :             osGeomFilter += "<ValueReference>";
     437             :         else
     438           5 :             osGeomFilter += "<PropertyName>";
     439           6 :         if (pszNS)
     440             :         {
     441           0 :             osGeomFilter += pszNS;
     442           0 :             osGeomFilter += ":";
     443             :         }
     444           6 :         osGeomFilter += osGeometryColumnName;
     445           6 :         if (atoi(poDS->GetVersion()) >= 2)
     446           1 :             osGeomFilter += "</ValueReference>";
     447             :         else
     448           5 :             osGeomFilter += "</PropertyName>";
     449             : 
     450           6 :         if (atoi(poDS->GetVersion()) >= 2)
     451             :         {
     452           1 :             osGeomFilter += "<gml:Envelope";
     453             : 
     454           2 :             CPLString osSRSName = CPLURLGetValue(pszBaseURL, "SRSNAME");
     455           1 :             if (!osSRSName.empty())
     456             :             {
     457           0 :                 osGeomFilter += " srsName=\"";
     458           0 :                 osGeomFilter += osSRSName;
     459           0 :                 osGeomFilter += "\"";
     460             :             }
     461             : 
     462           1 :             osGeomFilter += ">";
     463           1 :             if (bAxisOrderAlreadyInverted)
     464             :             {
     465             :                 osGeomFilter +=
     466             :                     CPLSPrintf("<gml:lowerCorner>%.16f "
     467             :                                "%.16f</gml:lowerCorner><gml:upperCorner>%.16f "
     468             :                                "%.16f</gml:upperCorner>",
     469             :                                oEnvelope.MinY, oEnvelope.MinX, oEnvelope.MaxY,
     470           1 :                                oEnvelope.MaxX);
     471             :             }
     472             :             else
     473             :                 osGeomFilter +=
     474             :                     CPLSPrintf("<gml:lowerCorner>%.16f "
     475             :                                "%.16f</gml:lowerCorner><gml:upperCorner>%.16f "
     476             :                                "%.16f</gml:upperCorner>",
     477             :                                oEnvelope.MinX, oEnvelope.MinY, oEnvelope.MaxX,
     478           0 :                                oEnvelope.MaxY);
     479           1 :             osGeomFilter += "</gml:Envelope>";
     480             :         }
     481           5 :         else if (poDS->RequiresEnvelopeSpatialFilter())
     482             :         {
     483           0 :             osGeomFilter += "<Envelope xmlns=\"http://www.opengis.net/gml\">";
     484           0 :             if (bAxisOrderAlreadyInverted)
     485             :             {
     486             :                 /* We can go here in WFS 1.1 with geographic coordinate systems
     487             :                  */
     488             :                 /* that are natively return in lat,long order, but as we have */
     489             :                 /* presented long,lat order to the user, we must switch back */
     490             :                 /* for the server... */
     491             :                 osGeomFilter +=
     492             :                     CPLSPrintf("<coord><X>%.16f</X><Y>%.16f</Y></"
     493             :                                "coord><coord><X>%.16f</X><Y>%.16f</Y></coord>",
     494             :                                oEnvelope.MinY, oEnvelope.MinX, oEnvelope.MaxY,
     495           0 :                                oEnvelope.MaxX);
     496             :             }
     497             :             else
     498             :                 osGeomFilter +=
     499             :                     CPLSPrintf("<coord><X>%.16f</X><Y>%.16f</Y></"
     500             :                                "coord><coord><X>%.16f</X><Y>%.16f</Y></coord>",
     501             :                                oEnvelope.MinX, oEnvelope.MinY, oEnvelope.MaxX,
     502           0 :                                oEnvelope.MaxY);
     503           0 :             osGeomFilter += "</Envelope>";
     504             :         }
     505             :         else
     506             :         {
     507           5 :             osGeomFilter += "<gml:Box>";
     508           5 :             osGeomFilter += "<gml:coordinates>";
     509           5 :             if (bAxisOrderAlreadyInverted)
     510             :             {
     511             :                 /* We can go here in WFS 1.1 with geographic coordinate systems
     512             :                  */
     513             :                 /* that are natively return in lat,long order, but as we have */
     514             :                 /* presented long,lat order to the user, we must switch back */
     515             :                 /* for the server... */
     516             :                 osGeomFilter +=
     517             :                     CPLSPrintf("%.16f,%.16f %.16f,%.16f", oEnvelope.MinY,
     518           5 :                                oEnvelope.MinX, oEnvelope.MaxY, oEnvelope.MaxX);
     519             :             }
     520             :             else
     521             :                 osGeomFilter +=
     522             :                     CPLSPrintf("%.16f,%.16f %.16f,%.16f", oEnvelope.MinX,
     523           0 :                                oEnvelope.MinY, oEnvelope.MaxX, oEnvelope.MaxY);
     524           5 :             osGeomFilter += "</gml:coordinates>";
     525           5 :             osGeomFilter += "</gml:Box>";
     526             :         }
     527           6 :         osGeomFilter += "</BBOX>";
     528             :     }
     529             : 
     530         147 :     if (!osGeomFilter.empty() || !osWFSWhere.empty())
     531             :     {
     532          34 :         CPLString osFilter;
     533          34 :         if (atoi(poDS->GetVersion()) >= 2)
     534           1 :             osFilter = "<Filter xmlns=\"http://www.opengis.net/fes/2.0\"";
     535             :         else
     536          33 :             osFilter = "<Filter xmlns=\"http://www.opengis.net/ogc\"";
     537          34 :         if (pszNS)
     538             :         {
     539           0 :             osFilter += " xmlns:";
     540           0 :             osFilter += pszNS;
     541           0 :             osFilter += "=\"";
     542           0 :             osFilter += pszNSVal;
     543           0 :             osFilter += "\"";
     544             :         }
     545          34 :         if (atoi(poDS->GetVersion()) >= 2)
     546           1 :             osFilter += " xmlns:gml=\"http://www.opengis.net/gml/3.2\">";
     547             :         else
     548          33 :             osFilter += " xmlns:gml=\"http://www.opengis.net/gml\">";
     549          34 :         if (!osGeomFilter.empty() && !osWFSWhere.empty())
     550           2 :             osFilter += "<And>";
     551          34 :         osFilter += osWFSWhere;
     552          34 :         osFilter += osGeomFilter;
     553          34 :         if (!osGeomFilter.empty() && !osWFSWhere.empty())
     554           2 :             osFilter += "</And>";
     555          34 :         osFilter += "</Filter>";
     556             : 
     557          34 :         osURL = CPLURLAddKVP(osURL, "FILTER", WFS_EscapeURL(osFilter));
     558             :     }
     559             : 
     560         147 :     if (bRequestHits)
     561             :     {
     562           9 :         osURL = CPLURLAddKVP(osURL, "RESULTTYPE", "hits");
     563             :     }
     564         138 :     else if (!aoSortColumns.empty())
     565             :     {
     566           0 :         CPLString osSortBy;
     567           0 :         for (const auto &sColumn : aoSortColumns)
     568             :         {
     569           0 :             if (!osSortBy.empty())
     570           0 :                 osSortBy += ',';
     571           0 :             osSortBy += sColumn.osColumn;
     572           0 :             if (!sColumn.bAsc)
     573             :             {
     574           0 :                 if (atoi(poDS->GetVersion()) >= 2)
     575           0 :                     osSortBy += " DESC";
     576             :                 else
     577           0 :                     osSortBy += " D";
     578             :             }
     579             :         }
     580           0 :         osURL = CPLURLAddKVP(osURL, "SORTBY", WFS_EscapeURL(osSortBy));
     581             :     }
     582             : 
     583             :     /* If no PROPERTYNAME is specified, build one if there are ignored fields */
     584         294 :     CPLString osPropertyName = CPLURLGetValue(osURL, "PROPERTYNAME");
     585         147 :     const char *pszPropertyName = osPropertyName.c_str();
     586         147 :     if (pszPropertyName[0] == 0 && poFeatureDefn != nullptr)
     587             :     {
     588         138 :         bool bHasIgnoredField = false;
     589         138 :         osPropertyName.clear();
     590        1054 :         for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
     591             :         {
     592         916 :             if (EQUAL(poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
     593             :                       "gml_id"))
     594             :             {
     595             :                 /* fake field : skip it */
     596             :             }
     597         807 :             else if (poFeatureDefn->GetFieldDefn(iField)->IsIgnored())
     598             :             {
     599          32 :                 bHasIgnoredField = true;
     600             :             }
     601             :             else
     602             :             {
     603         775 :                 if (!osPropertyName.empty())
     604         670 :                     osPropertyName += ",";
     605             :                 osPropertyName +=
     606         775 :                     poFeatureDefn->GetFieldDefn(iField)->GetNameRef();
     607             :             }
     608             :         }
     609         138 :         if (!osGeometryColumnName.empty())
     610             :         {
     611         105 :             if (poFeatureDefn->IsGeometryIgnored())
     612             :             {
     613           0 :                 bHasIgnoredField = true;
     614             :             }
     615             :             else
     616             :             {
     617         105 :                 if (!osPropertyName.empty())
     618         103 :                     osPropertyName += ",";
     619         105 :                 osPropertyName += osGeometryColumnName;
     620             :             }
     621             :         }
     622             : 
     623         138 :         if (bHasIgnoredField && !osPropertyName.empty())
     624             :         {
     625           8 :             osURL = CPLURLAddKVP(osURL, "PROPERTYNAME",
     626          12 :                                  WFS_EscapeURL(osPropertyName));
     627             :         }
     628             :     }
     629             : 
     630         294 :     return osURL;
     631             : }
     632             : 
     633             : /************************************************************************/
     634             : /*               OGRWFSFetchContentDispositionFilename()                */
     635             : /************************************************************************/
     636             : 
     637          52 : static const char *OGRWFSFetchContentDispositionFilename(char **papszHeaders)
     638             : {
     639             :     const char *pszContentDisposition =
     640          52 :         CSLFetchNameValue(papszHeaders, "Content-Disposition");
     641          52 :     if (pszContentDisposition &&
     642           6 :         STARTS_WITH(pszContentDisposition, "attachment; filename="))
     643             :     {
     644           6 :         return pszContentDisposition + strlen("attachment; filename=");
     645             :     }
     646          46 :     return nullptr;
     647             : }
     648             : 
     649             : /************************************************************************/
     650             : /*                   MustRetryIfNonCompliantServer()                    */
     651             : /************************************************************************/
     652             : 
     653          64 : bool OGRWFSLayer::MustRetryIfNonCompliantServer(const char *pszServerAnswer)
     654             : {
     655          64 :     bool bRetry = false;
     656             : 
     657             :     /* Deegree server does not support PropertyIsNotEqualTo */
     658             :     /* We have to turn it into <Not><PropertyIsEqualTo> */
     659          77 :     if (!osWFSWhere.empty() && poDS->PropertyIsNotEqualToSupported() &&
     660          13 :         strstr(pszServerAnswer,
     661             :                "Unknown comparison operation: 'PropertyIsNotEqualTo'") !=
     662             :             nullptr)
     663             :     {
     664           0 :         poDS->SetPropertyIsNotEqualToUnSupported();
     665           0 :         bRetry = true;
     666             :     }
     667             : 
     668             :     /* Deegree server requires the gml: prefix in GmlObjectId element, but ESRI
     669             :      */
     670             :     /* doesn't like it at all ! Other servers don't care... */
     671          77 :     if (!osWFSWhere.empty() && !poDS->DoesGmlObjectIdNeedGMLPrefix() &&
     672          13 :         strstr(pszServerAnswer,
     673             :                "&lt;GmlObjectId&gt; requires 'gml:id'-attribute!") != nullptr)
     674             :     {
     675           0 :         poDS->SetGmlObjectIdNeedsGMLPrefix();
     676           0 :         bRetry = true;
     677             :     }
     678             : 
     679             :     /* GeoServer can return the error 'Only FeatureIds are supported when
     680             :      * encoding id filters to SDE' */
     681          77 :     if (!osWFSWhere.empty() && !bUseFeatureIdAtLayerLevel &&
     682          13 :         strstr(pszServerAnswer, "Only FeatureIds are supported") != nullptr)
     683             :     {
     684           0 :         bUseFeatureIdAtLayerLevel = true;
     685           0 :         bRetry = true;
     686             :     }
     687             : 
     688          64 :     if (bRetry)
     689             :     {
     690           0 :         SetAttributeFilter(osSQLWhere);
     691           0 :         bHasFetched = true;
     692           0 :         bReloadNeeded = false;
     693             :     }
     694             : 
     695          64 :     return bRetry;
     696             : }
     697             : 
     698             : /************************************************************************/
     699             : /*                          FetchGetFeature()                           */
     700             : /************************************************************************/
     701             : 
     702         122 : GDALDataset *OGRWFSLayer::FetchGetFeature(int nRequestMaxFeatures)
     703             : {
     704             : 
     705         244 :     CPLString osURL = MakeGetFeatureURL(nRequestMaxFeatures, FALSE);
     706         122 :     CPLDebug("WFS", "%s", osURL.c_str());
     707             : 
     708         122 :     CPLHTTPResult *psResult = nullptr;
     709             : 
     710         244 :     CPLString osOutputFormat = CPLURLGetValue(osURL, "OUTPUTFORMAT");
     711             : 
     712          84 :     const auto ReadNumberMatched = [this](const char *pszData)
     713             :     {
     714          76 :         const char *pszNumberMatched = strstr(pszData, " numberMatched=\"");
     715          76 :         if (!pszNumberMatched)
     716          69 :             pszNumberMatched = strstr(pszData, "\n"
     717             :                                                "numberMatched=\"");
     718          76 :         if (pszNumberMatched)
     719             :         {
     720          19 :             pszNumberMatched += strlen(" numberMatched=\"");
     721          19 :             if (*pszNumberMatched >= '0' && *pszNumberMatched <= '9')
     722             :             {
     723           2 :                 m_nNumberMatched = CPLAtoGIntBig(pszNumberMatched);
     724           2 :                 CPLDebug("WFS", "numberMatched = " CPL_FRMT_GIB,
     725             :                          m_nNumberMatched);
     726           2 :                 if (!bCountFeaturesInGetNextFeature)
     727           2 :                     nFeatures = m_nNumberMatched;
     728             :             }
     729             :         }
     730         198 :     };
     731             : 
     732         122 :     if (CPLTestBool(CPLGetConfigOption("OGR_WFS_USE_STREAMING", "YES")))
     733             :     {
     734             :         CPLConfigOptionSetter oDisableVSICACHE(
     735          71 :             "VSI_CACHE", "FALSE", /* bSetOnlyIfUndefined = */ false);
     736             : 
     737          71 :         CPLString osStreamingName;
     738         139 :         if (STARTS_WITH(osURL, "/vsimem/") &&
     739          68 :             CPLTestBool(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")))
     740             :         {
     741          68 :             osStreamingName = osURL;
     742             :         }
     743             :         else
     744             :         {
     745           3 :             osStreamingName += "/vsicurl_streaming/";
     746           3 :             osStreamingName += osURL;
     747             :         }
     748             : 
     749             :         /* Try streaming when the output format is GML and that we have a .xsd
     750             :          */
     751             :         /* that we are able to understand */
     752          71 :         CPLString osXSDFileName = CPLSPrintf("%s/file.xsd", m_osTmpDir.c_str());
     753             :         VSIStatBufL sBuf;
     754          71 :         GDALDriver *poDriver = nullptr;
     755          71 :         if ((osOutputFormat.empty() ||
     756           6 :              osOutputFormat.ifind("GML") != std::string::npos) &&
     757         110 :             VSIStatL(osXSDFileName, &sBuf) == 0 &&
     758          45 :             (poDriver = GDALDriver::FromHandle(GDALGetDriverByName("GML"))) !=
     759         142 :                 nullptr &&
     760          44 :             poDriver->pfnOpen)
     761             :         {
     762          44 :             bStreamingDS = true;
     763          44 :             CPLStringList aosOptions;
     764          44 :             aosOptions.SetNameValue("XSD", osXSDFileName.c_str());
     765             :             aosOptions.SetNameValue("EMPTY_AS_NULL",
     766          44 :                                     poDS->IsEmptyAsNull() ? "YES" : "NO");
     767          44 :             if (CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG",
     768          44 :                                    nullptr) == nullptr)
     769             :             {
     770             :                 aosOptions.SetNameValue(
     771             :                     "INVERT_AXIS_ORDER_IF_LAT_LONG",
     772          44 :                     poDS->InvertAxisOrderIfLatLong() ? "YES" : "NO");
     773             :             }
     774          44 :             if (CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", nullptr) ==
     775             :                 nullptr)
     776             :             {
     777             :                 aosOptions.SetNameValue("CONSIDER_EPSG_AS_URN",
     778          44 :                                         poDS->GetConsiderEPSGAsURN().c_str());
     779             :             }
     780          44 :             if (CPLGetConfigOption("GML_EXPOSE_GML_ID", nullptr) == nullptr)
     781             :             {
     782             :                 aosOptions.SetNameValue("EXPOSE_GML_ID",
     783          44 :                                         poDS->ExposeGMLId() ? "YES" : "NO");
     784             :                 // iGMLOOIdex ++;
     785             :             }
     786             : 
     787          44 :             GDALOpenInfo oOpenInfo(osStreamingName.c_str(), GA_ReadOnly);
     788          44 :             if (oOpenInfo.nHeaderBytes && m_nNumberMatched < 0)
     789             :             {
     790          29 :                 const char *pszData =
     791             :                     reinterpret_cast<const char *>(oOpenInfo.pabyHeader);
     792          29 :                 ReadNumberMatched(pszData);
     793             :             }
     794          44 :             oOpenInfo.papszOpenOptions = aosOptions.List();
     795             : 
     796          44 :             auto poOutputDS = poDriver->Open(&oOpenInfo, true);
     797          44 :             if (poOutputDS)
     798             :             {
     799          29 :                 return poOutputDS;
     800             :             }
     801             :         }
     802             :         /* Try streaming when the output format is FlatGeobuf */
     803          27 :         else if ((osOutputFormat.empty() ||
     804           6 :                   osOutputFormat.ifind("flatgeobuf") != std::string::npos) &&
     805          35 :                  VSIStatL(osXSDFileName, &sBuf) == 0 &&
     806           2 :                  GDALGetDriverByName("FlatGeobuf") != nullptr)
     807             :         {
     808           2 :             bStreamingDS = true;
     809           2 :             const char *const apszAllowedDrivers[] = {"FlatGeobuf", nullptr};
     810             : 
     811             :             GDALDataset *poFlatGeobuf_DS =
     812           2 :                 GDALDataset::Open(osStreamingName, GDAL_OF_VECTOR,
     813             :                                   apszAllowedDrivers, nullptr, nullptr);
     814           2 :             if (poFlatGeobuf_DS)
     815             :             {
     816           1 :                 return poFlatGeobuf_DS;
     817             :             }
     818             :         }
     819             :         else
     820             :         {
     821          25 :             bStreamingDS = false;
     822             :         }
     823             : 
     824             :         {
     825             :             /* In case of failure, read directly the content to examine */
     826             :             /* it, if it is XML error content */
     827             :             char szBuffer[2048];
     828          41 :             int nRead = 0;
     829          41 :             VSILFILE *fp = VSIFOpenL(osStreamingName, "rb");
     830          41 :             if (fp)
     831             :             {
     832          15 :                 nRead = static_cast<int>(
     833          15 :                     VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fp));
     834          15 :                 szBuffer[nRead] = '\0';
     835          15 :                 VSIFCloseL(fp);
     836             :             }
     837             : 
     838          41 :             if (nRead != 0)
     839             :             {
     840          15 :                 if (strstr(szBuffer, "<wfs:FeatureCollection") != nullptr)
     841             :                 {
     842             :                     poDriver =
     843           8 :                         GDALDriver::FromHandle(GDALGetDriverByName("GML"));
     844           8 :                     if (poDriver == nullptr)
     845             :                     {
     846           1 :                         CPLError(
     847             :                             CE_Failure, CPLE_AppDefined,
     848             :                             "Cannot read features as GML driver is missing");
     849           2 :                         return nullptr;
     850             :                     }
     851           7 :                     else if (poDriver->pfnOpen == nullptr)
     852             :                     {
     853           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     854             :                                  "Cannot read features as GML driver has no "
     855             :                                  "read support");
     856           0 :                         return nullptr;
     857             :                     }
     858             :                 }
     859             : 
     860          14 :                 if (MustRetryIfNonCompliantServer(szBuffer))
     861           0 :                     return FetchGetFeature(nRequestMaxFeatures);
     862             : 
     863          14 :                 if (strstr(szBuffer, "<ServiceExceptionReport") != nullptr ||
     864          13 :                     strstr(szBuffer, "<ows:ExceptionReport") != nullptr)
     865             :                 {
     866           1 :                     if (poDS->IsOldDeegree(szBuffer))
     867             :                     {
     868           0 :                         return FetchGetFeature(nRequestMaxFeatures);
     869             :                     }
     870             : 
     871           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     872             :                              "Error returned by server : %s", szBuffer);
     873           1 :                     return nullptr;
     874             :                 }
     875             :             }
     876             :         }
     877             :     }
     878             : 
     879          90 :     bStreamingDS = false;
     880          90 :     psResult = poDS->HTTPFetch(osURL, nullptr);
     881          90 :     if (psResult == nullptr)
     882             :     {
     883          40 :         return nullptr;
     884             :     }
     885             : 
     886          50 :     const char *pszContentType = "";
     887          50 :     if (psResult->pszContentType)
     888           6 :         pszContentType = psResult->pszContentType;
     889             : 
     890          50 :     VSIMkdir(m_osTmpDir.c_str(), 0);
     891             : 
     892          50 :     GByte *pabyData = psResult->pabyData;
     893          50 :     int nDataLen = psResult->nDataLen;
     894          50 :     bool bIsMultiPart = false;
     895          50 :     const char *pszAttachmentFilename = nullptr;
     896             : 
     897          56 :     if (strstr(pszContentType, "multipart") &&
     898           6 :         CPLHTTPParseMultipartMime(psResult))
     899             :     {
     900           6 :         bIsMultiPart = true;
     901           6 :         VSIRmdirRecursive(m_osTmpDir.c_str());
     902           6 :         VSIMkdir(m_osTmpDir.c_str(), 0);
     903          14 :         for (int i = 0; i < psResult->nMimePartCount; i++)
     904             :         {
     905          16 :             CPLString osTmpFileName = m_osTmpDir + "/";
     906          16 :             pszAttachmentFilename = OGRWFSFetchContentDispositionFilename(
     907           8 :                 psResult->pasMimePart[i].papszHeaders);
     908             : 
     909           8 :             if (pszAttachmentFilename)
     910           6 :                 osTmpFileName += pszAttachmentFilename;
     911             :             else
     912           2 :                 osTmpFileName += CPLSPrintf("file_%d", i);
     913             : 
     914             :             GByte *pData = static_cast<GByte *>(
     915           8 :                 VSI_MALLOC_VERBOSE(psResult->pasMimePart[i].nDataLen));
     916           8 :             if (pData)
     917             :             {
     918           8 :                 memcpy(pData, psResult->pasMimePart[i].pabyData,
     919           8 :                        psResult->pasMimePart[i].nDataLen);
     920           8 :                 VSILFILE *fp = VSIFileFromMemBuffer(
     921           8 :                     osTmpFileName, pData, psResult->pasMimePart[i].nDataLen,
     922             :                     TRUE);
     923           8 :                 VSIFCloseL(fp);
     924             :             }
     925             :         }
     926             :     }
     927             :     else
     928             :         pszAttachmentFilename =
     929          44 :             OGRWFSFetchContentDispositionFilename(psResult->papszHeaders);
     930             : 
     931          50 :     bool bJSON = false;
     932          50 :     bool bCSV = false;
     933          50 :     bool bKML = false;
     934          50 :     bool bKMZ = false;
     935          50 :     bool bFlatGeobuf = false;
     936          50 :     bool bZIP = false;
     937          50 :     bool bGZIP = false;
     938             : 
     939          50 :     const char *pszOutputFormat = osOutputFormat.c_str();
     940             : 
     941         100 :     if (FindSubStringInsensitive(pszContentType, "json") ||
     942          50 :         FindSubStringInsensitive(pszOutputFormat, "json"))
     943             :     {
     944           4 :         bJSON = true;
     945             :     }
     946          92 :     else if (FindSubStringInsensitive(pszContentType, "csv") ||
     947          46 :              FindSubStringInsensitive(pszOutputFormat, "csv"))
     948             :     {
     949           0 :         bCSV = true;
     950             :     }
     951          92 :     else if (FindSubStringInsensitive(pszContentType, "kml") ||
     952          46 :              FindSubStringInsensitive(pszOutputFormat, "kml"))
     953             :     {
     954           0 :         bKML = true;
     955             :     }
     956          92 :     else if (FindSubStringInsensitive(pszContentType, "kmz") ||
     957          46 :              FindSubStringInsensitive(pszOutputFormat, "kmz"))
     958             :     {
     959           0 :         bKMZ = true;
     960             :     }
     961          92 :     else if (FindSubStringInsensitive(pszContentType, "flatgeobuf") ||
     962          46 :              FindSubStringInsensitive(pszOutputFormat, "flatgeobuf"))
     963             :     {
     964           0 :         bFlatGeobuf = true;
     965             :     }
     966          46 :     else if (strstr(pszContentType, "application/zip") != nullptr)
     967             :     {
     968           0 :         bZIP = true;
     969             :     }
     970          46 :     else if (strstr(pszContentType, "application/gzip") != nullptr)
     971             :     {
     972           0 :         bGZIP = true;
     973             :     }
     974             : 
     975          50 :     const char *pszData = reinterpret_cast<const char *>(pabyData);
     976          50 :     if (MustRetryIfNonCompliantServer(pszData))
     977             :     {
     978           0 :         CPLHTTPDestroyResult(psResult);
     979           0 :         return FetchGetFeature(nRequestMaxFeatures);
     980             :     }
     981             : 
     982          50 :     if (strstr(pszData, "<ServiceExceptionReport") != nullptr ||
     983          49 :         strstr(pszData, "<ows:ExceptionReport") != nullptr)
     984             :     {
     985           1 :         if (poDS->IsOldDeegree(pszData))
     986             :         {
     987           0 :             CPLHTTPDestroyResult(psResult);
     988           0 :             return FetchGetFeature(nRequestMaxFeatures);
     989             :         }
     990             : 
     991           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
     992             :                  pabyData);
     993           1 :         CPLHTTPDestroyResult(psResult);
     994           1 :         return nullptr;
     995             :     }
     996             : 
     997          49 :     if (m_nNumberMatched < 0)
     998          47 :         ReadNumberMatched(pszData);
     999             : 
    1000          98 :     CPLString osTmpFileName;
    1001             : 
    1002          49 :     if (!bIsMultiPart)
    1003             :     {
    1004          43 :         if (bJSON)
    1005           4 :             osTmpFileName = m_osTmpDir + "/file.geojson";
    1006          39 :         else if (bZIP)
    1007           0 :             osTmpFileName = m_osTmpDir + "/file.zip";
    1008          39 :         else if (bCSV)
    1009           0 :             osTmpFileName = m_osTmpDir + "/file.csv";
    1010          39 :         else if (bKML)
    1011           0 :             osTmpFileName = m_osTmpDir + "/file.kml";
    1012          39 :         else if (bKMZ)
    1013           0 :             osTmpFileName = m_osTmpDir + "/file.kmz";
    1014          39 :         else if (bFlatGeobuf)
    1015           0 :             osTmpFileName = m_osTmpDir + "/file.fgb";
    1016             :         /* GML is a special case. It needs the .xsd file that has been saved */
    1017             :         /* as file.xsd, so we cannot used the attachment filename */
    1018          39 :         else if (pszAttachmentFilename &&
    1019          39 :                  !EQUAL(CPLGetExtensionSafe(pszAttachmentFilename).c_str(),
    1020             :                         "GML"))
    1021             :         {
    1022           0 :             osTmpFileName = m_osTmpDir + "/";
    1023           0 :             osTmpFileName += pszAttachmentFilename;
    1024             :         }
    1025             :         else
    1026             :         {
    1027          39 :             osTmpFileName = m_osTmpDir + "/file.gfs";
    1028          39 :             VSIUnlink(osTmpFileName);
    1029             : 
    1030          39 :             osTmpFileName = m_osTmpDir + "/file.gml";
    1031             :         }
    1032             : 
    1033             :         VSILFILE *fp =
    1034          43 :             VSIFileFromMemBuffer(osTmpFileName, pabyData, nDataLen, TRUE);
    1035          43 :         VSIFCloseL(fp);
    1036          43 :         psResult->pabyData = nullptr;
    1037             : 
    1038          43 :         if (bZIP)
    1039             :         {
    1040           0 :             osTmpFileName = "/vsizip/" + osTmpFileName;
    1041             :         }
    1042          43 :         else if (bGZIP)
    1043             :         {
    1044           0 :             osTmpFileName = "/vsigzip/" + osTmpFileName;
    1045             :         }
    1046             :     }
    1047             :     else
    1048             :     {
    1049           6 :         pabyData = nullptr;
    1050           6 :         nDataLen = 0;
    1051           6 :         osTmpFileName = m_osTmpDir;
    1052             :     }
    1053             : 
    1054          49 :     CPLHTTPDestroyResult(psResult);
    1055             : 
    1056          49 :     const char *const *papszOpenOptions = nullptr;
    1057          49 :     const char *apszGMLOpenOptions[4] = {nullptr, nullptr, nullptr, nullptr};
    1058          49 :     int iGMLOOIdex = 0;
    1059          49 :     if (CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", nullptr) ==
    1060             :         nullptr)
    1061             :     {
    1062          49 :         apszGMLOpenOptions[iGMLOOIdex] =
    1063          49 :             CPLSPrintf("INVERT_AXIS_ORDER_IF_LAT_LONG=%s",
    1064          49 :                        poDS->InvertAxisOrderIfLatLong() ? "YES" : "NO");
    1065          49 :         iGMLOOIdex++;
    1066             :     }
    1067          49 :     if (CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", nullptr) == nullptr)
    1068             :     {
    1069          49 :         apszGMLOpenOptions[iGMLOOIdex] = CPLSPrintf(
    1070          49 :             "CONSIDER_EPSG_AS_URN=%s", poDS->GetConsiderEPSGAsURN().c_str());
    1071          49 :         iGMLOOIdex++;
    1072             :     }
    1073          49 :     if (CPLGetConfigOption("GML_EXPOSE_GML_ID", nullptr) == nullptr)
    1074             :     {
    1075          49 :         apszGMLOpenOptions[iGMLOOIdex] =
    1076          49 :             CPLSPrintf("EXPOSE_GML_ID=%s", poDS->ExposeGMLId() ? "YES" : "NO");
    1077             :         // iGMLOOIdex ++;
    1078             :     }
    1079             : 
    1080          49 :     GDALDriverH hDrv = GDALIdentifyDriver(osTmpFileName, nullptr);
    1081          49 :     if (hDrv != nullptr && hDrv == GDALGetDriverByName("GML"))
    1082          37 :         papszOpenOptions = apszGMLOpenOptions;
    1083             : 
    1084             :     auto poPageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    1085          98 :         osTmpFileName, GDAL_OF_VECTOR, nullptr, papszOpenOptions, nullptr));
    1086          49 :     if (poPageDS == nullptr && (bZIP || bIsMultiPart))
    1087             :     {
    1088           4 :         char **papszFileList = VSIReadDir(osTmpFileName);
    1089           4 :         for (int i = 0; papszFileList != nullptr && papszFileList[i] != nullptr;
    1090             :              i++)
    1091             :         {
    1092             :             const CPLString osFullFilename =
    1093           4 :                 CPLFormFilenameSafe(osTmpFileName, papszFileList[i], nullptr);
    1094           4 :             hDrv = GDALIdentifyDriver(osFullFilename, nullptr);
    1095           4 :             if (hDrv != nullptr && hDrv == GDALGetDriverByName("GML"))
    1096           0 :                 papszOpenOptions = apszGMLOpenOptions;
    1097           4 :             poPageDS.reset(GDALDataset::Open(osFullFilename, GDAL_OF_VECTOR,
    1098             :                                              nullptr, papszOpenOptions,
    1099             :                                              nullptr));
    1100           4 :             if (poPageDS != nullptr)
    1101           4 :                 break;
    1102             :         }
    1103             : 
    1104           4 :         CSLDestroy(papszFileList);
    1105             :     }
    1106             : 
    1107          49 :     if (poPageDS == nullptr)
    1108             :     {
    1109           2 :         if (pabyData != nullptr && !bJSON && !bZIP &&
    1110           2 :             strstr(reinterpret_cast<const char *>(pabyData),
    1111           2 :                    "<wfs:FeatureCollection") == nullptr &&
    1112           2 :             strstr(reinterpret_cast<const char *>(pabyData),
    1113             :                    "<gml:FeatureCollection") == nullptr)
    1114             :         {
    1115           2 :             if (nDataLen > 1000)
    1116           0 :                 pabyData[1000] = 0;
    1117           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
    1118             :                      pabyData);
    1119             :         }
    1120           2 :         return nullptr;
    1121             :     }
    1122             : 
    1123          47 :     OGRLayer *poLayer = poPageDS->GetLayer(0);
    1124          47 :     if (poLayer == nullptr)
    1125             :     {
    1126           0 :         return nullptr;
    1127             :     }
    1128             : 
    1129          47 :     return poPageDS.release();
    1130             : }
    1131             : 
    1132             : /************************************************************************/
    1133             : /*                            GetLayerDefn()                            */
    1134             : /************************************************************************/
    1135             : 
    1136        2096 : const OGRFeatureDefn *OGRWFSLayer::GetLayerDefn() const
    1137             : {
    1138        2096 :     if (!poFeatureDefn)
    1139             :     {
    1140         133 :         const_cast<OGRWFSLayer *>(this)->BuildLayerDefn();
    1141             :     }
    1142        2096 :     return poFeatureDefn;
    1143             : }
    1144             : 
    1145             : /************************************************************************/
    1146             : /*                           BuildLayerDefn()                           */
    1147             : /************************************************************************/
    1148             : 
    1149         133 : void OGRWFSLayer::BuildLayerDefn()
    1150             : {
    1151         133 :     CPLAssert(!poFeatureDefn);
    1152             : 
    1153         133 :     if (poDS->GetLayerCount() > 1)
    1154             :     {
    1155          53 :         poDS->LoadMultipleLayerDefn(GetName(), pszNS, pszNSVal);
    1156             :     }
    1157             : 
    1158         133 :     if (!poFeatureDefn)
    1159         105 :         BuildLayerDefn(nullptr);
    1160         133 : }
    1161             : 
    1162             : /************************************************************************/
    1163             : /*                           BuildLayerDefn()                           */
    1164             : /************************************************************************/
    1165             : 
    1166         167 : OGRFeatureDefn *OGRWFSLayer::BuildLayerDefn(OGRFeatureDefn *poSrcFDefn)
    1167             : {
    1168         167 :     bool bUnsetWidthPrecision = false;
    1169             : 
    1170         167 :     poFeatureDefn = new OGRFeatureDefn(pszName);
    1171         167 :     poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
    1172         167 :     poFeatureDefn->Reference();
    1173             : 
    1174         167 :     GDALDataset *l_poDS = nullptr;
    1175             : 
    1176         167 :     if (poSrcFDefn == nullptr)
    1177         105 :         poSrcFDefn = DescribeFeatureType();
    1178         167 :     if (poSrcFDefn == nullptr)
    1179             :     {
    1180          24 :         l_poDS = FetchGetFeature(1);
    1181          24 :         if (l_poDS == nullptr)
    1182             :         {
    1183          20 :             return poFeatureDefn;
    1184             :         }
    1185           4 :         OGRLayer *l_poLayer = l_poDS->GetLayer(0);
    1186           4 :         if (l_poLayer == nullptr)
    1187             :         {
    1188           0 :             return poFeatureDefn;
    1189             :         }
    1190           4 :         poSrcFDefn = l_poLayer->GetLayerDefn();
    1191           4 :         osGeometryColumnName = l_poLayer->GetGeometryColumn();
    1192           4 :         bGotApproximateLayerDefn = true;
    1193             :         /* We cannot trust width and precision based on a single feature */
    1194           4 :         bUnsetWidthPrecision = true;
    1195             :     }
    1196             : 
    1197             :     const CPLStringList aosPropertyName(CSLTokenizeString2(
    1198         147 :         CPLURLGetValue(pszBaseURL, "PROPERTYNAME"), "(,)", 0));
    1199             : 
    1200         147 :     poFeatureDefn->SetGeomType(poSrcFDefn->GetGeomType());
    1201         147 :     if (poSrcFDefn->GetGeomFieldCount() > 0)
    1202         264 :         poFeatureDefn->GetGeomFieldDefn(0)->SetName(
    1203         132 :             poSrcFDefn->GetGeomFieldDefn(0)->GetNameRef());
    1204        1000 :     for (int i = 0; i < poSrcFDefn->GetFieldCount(); i++)
    1205             :     {
    1206         853 :         if (!aosPropertyName.empty())
    1207             :         {
    1208           0 :             if (aosPropertyName.FindString(
    1209           0 :                     poSrcFDefn->GetFieldDefn(i)->GetNameRef()) >= 0)
    1210           0 :                 poFeatureDefn->AddFieldDefn(poSrcFDefn->GetFieldDefn(i));
    1211             :             else
    1212           0 :                 bGotApproximateLayerDefn = true;
    1213             :         }
    1214             :         else
    1215             :         {
    1216        1706 :             OGRFieldDefn oFieldDefn(poSrcFDefn->GetFieldDefn(i));
    1217         853 :             if (bUnsetWidthPrecision)
    1218             :             {
    1219          20 :                 oFieldDefn.SetWidth(0);
    1220          20 :                 oFieldDefn.SetPrecision(0);
    1221             :             }
    1222         853 :             poFeatureDefn->AddFieldDefn(&oFieldDefn);
    1223             :         }
    1224             :     }
    1225             : 
    1226         147 :     if (l_poDS)
    1227           4 :         GDALClose(l_poDS);
    1228             :     else
    1229         143 :         delete poSrcFDefn;
    1230             : 
    1231         147 :     return poFeatureDefn;
    1232             : }
    1233             : 
    1234             : /************************************************************************/
    1235             : /*                            ResetReading()                            */
    1236             : /************************************************************************/
    1237             : 
    1238         202 : void OGRWFSLayer::ResetReading()
    1239             : 
    1240             : {
    1241         202 :     if (poFeatureDefn == nullptr)
    1242           7 :         return;
    1243         195 :     if (bPagingActive)
    1244           0 :         bReloadNeeded = true;
    1245         195 :     nPagingStartIndex = 0;
    1246         195 :     nFeatureRead = 0;
    1247         195 :     m_nNumberMatched = -1;
    1248         195 :     m_bHasReadAtLeastOneFeatureInThisPage = false;
    1249         195 :     if (bReloadNeeded)
    1250             :     {
    1251          42 :         GDALClose(poBaseDS);
    1252          42 :         poBaseDS = nullptr;
    1253          42 :         poBaseLayer = nullptr;
    1254          42 :         bHasFetched = false;
    1255          42 :         bReloadNeeded = false;
    1256             :     }
    1257         195 :     if (poBaseLayer)
    1258          52 :         poBaseLayer->ResetReading();
    1259             : }
    1260             : 
    1261             : /************************************************************************/
    1262             : /*                          SetIgnoredFields()                          */
    1263             : /************************************************************************/
    1264             : 
    1265          16 : OGRErr OGRWFSLayer::SetIgnoredFields(CSLConstList papszFields)
    1266             : {
    1267          16 :     bReloadNeeded = true;
    1268          16 :     ResetReading();
    1269          16 :     return OGRLayer::SetIgnoredFields(papszFields);
    1270             : }
    1271             : 
    1272             : /************************************************************************/
    1273             : /*                           GetNextFeature()                           */
    1274             : /************************************************************************/
    1275             : 
    1276         128 : OGRFeature *OGRWFSLayer::GetNextFeature()
    1277             : {
    1278         128 :     GetLayerDefn();
    1279             : 
    1280             :     while (true)
    1281             :     {
    1282         141 :         if (bReloadNeeded)
    1283             :         {
    1284          40 :             m_bHasReadAtLeastOneFeatureInThisPage = false;
    1285          40 :             GDALClose(poBaseDS);
    1286          40 :             poBaseDS = nullptr;
    1287          40 :             poBaseLayer = nullptr;
    1288          40 :             bHasFetched = false;
    1289          40 :             bReloadNeeded = false;
    1290             :         }
    1291         141 :         if (poBaseDS == nullptr && !bHasFetched)
    1292             :         {
    1293          98 :             bHasFetched = true;
    1294          98 :             poBaseDS = FetchGetFeature(0);
    1295          98 :             poBaseLayer = nullptr;
    1296          98 :             if (poBaseDS)
    1297             :             {
    1298          73 :                 poBaseLayer = poBaseDS->GetLayer(0);
    1299          73 :                 if (poBaseLayer == nullptr)
    1300           0 :                     return nullptr;
    1301          73 :                 poBaseLayer->ResetReading();
    1302             : 
    1303             :                 /* Check that the layer field definition is consistent with the
    1304             :                  * one */
    1305             :                 /* we got in BuildLayerDefn() */
    1306          73 :                 if (poFeatureDefn->GetFieldCount() !=
    1307          73 :                     poBaseLayer->GetLayerDefn()->GetFieldCount())
    1308           6 :                     bGotApproximateLayerDefn = true;
    1309             :                 else
    1310             :                 {
    1311         579 :                     for (int iField = 0;
    1312         579 :                          iField < poFeatureDefn->GetFieldCount(); iField++)
    1313             :                     {
    1314             :                         OGRFieldDefn *poFDefn1 =
    1315         518 :                             poFeatureDefn->GetFieldDefn(iField);
    1316             :                         OGRFieldDefn *poFDefn2 =
    1317         518 :                             poBaseLayer->GetLayerDefn()->GetFieldDefn(iField);
    1318         518 :                         if (strcmp(poFDefn1->GetNameRef(),
    1319        1030 :                                    poFDefn2->GetNameRef()) != 0 ||
    1320         512 :                             poFDefn1->GetType() != poFDefn2->GetType())
    1321             :                         {
    1322           6 :                             bGotApproximateLayerDefn = true;
    1323           6 :                             break;
    1324             :                         }
    1325             :                     }
    1326             :                 }
    1327             :             }
    1328             :         }
    1329         141 :         if (poBaseDS == nullptr || poBaseLayer == nullptr)
    1330          38 :             return nullptr;
    1331             : 
    1332         103 :         OGRFeature *poSrcFeature = poBaseLayer->GetNextFeature();
    1333         103 :         if (poSrcFeature == nullptr)
    1334             :         {
    1335          24 :             if (bPagingActive && m_bHasReadAtLeastOneFeatureInThisPage &&
    1336          14 :                 (m_nNumberMatched < 0 || nFeatureRead < m_nNumberMatched))
    1337             :             {
    1338          12 :                 bReloadNeeded = true;
    1339          12 :                 nPagingStartIndex = nFeatureRead;
    1340          12 :                 continue;
    1341             :             }
    1342          12 :             return nullptr;
    1343             :         }
    1344          79 :         nFeatureRead++;
    1345          79 :         m_bHasReadAtLeastOneFeatureInThisPage = true;
    1346          79 :         if (bCountFeaturesInGetNextFeature)
    1347           2 :             nFeatures++;
    1348             : 
    1349          79 :         OGRGeometry *poGeom = poSrcFeature->GetGeometryRef();
    1350          86 :         if (m_poFilterGeom != nullptr && poGeom != nullptr &&
    1351           7 :             !FilterGeometry(poGeom))
    1352             :         {
    1353           0 :             delete poSrcFeature;
    1354           0 :             continue;
    1355             :         }
    1356             : 
    1357             :         /* Client-side attribute filtering with underlying layer defn */
    1358             :         /* identical to exposed layer defn. */
    1359          64 :         if (!bGotApproximateLayerDefn && osWFSWhere.empty() &&
    1360         143 :             m_poAttrQuery != nullptr && !m_poAttrQuery->Evaluate(poSrcFeature))
    1361             :         {
    1362           0 :             delete poSrcFeature;
    1363           0 :             continue;
    1364             :         }
    1365             : 
    1366          79 :         OGRFeature *poNewFeature = new OGRFeature(poFeatureDefn);
    1367          79 :         if (bGotApproximateLayerDefn)
    1368             :         {
    1369          15 :             poNewFeature->SetFrom(poSrcFeature);
    1370          15 :             poNewFeature->SetFID(poSrcFeature->GetFID());
    1371             : 
    1372             :             /* Client-side attribute filtering. */
    1373          17 :             if (m_poAttrQuery != nullptr && osWFSWhere.empty() &&
    1374           2 :                 !m_poAttrQuery->Evaluate(poNewFeature))
    1375             :             {
    1376           1 :                 delete poSrcFeature;
    1377           1 :                 delete poNewFeature;
    1378           1 :                 continue;
    1379             :             }
    1380             :         }
    1381             :         else
    1382             :         {
    1383          64 :             poNewFeature->SetFID(poSrcFeature->GetFID());
    1384         604 :             for (int iField = 0; iField < poFeatureDefn->GetFieldCount();
    1385             :                  iField++)
    1386             :             {
    1387         540 :                 poNewFeature->SetField(iField,
    1388         540 :                                        poSrcFeature->GetRawFieldRef(iField));
    1389             :             }
    1390          64 :             poNewFeature->SetStyleString(poSrcFeature->GetStyleString());
    1391          64 :             poNewFeature->SetGeometryDirectly(poSrcFeature->StealGeometry());
    1392             :         }
    1393          78 :         poGeom = poNewFeature->GetGeometryRef();
    1394             : 
    1395             :         /* FIXME? I don't really know what we should do with WFS 1.1.0 */
    1396             :         /* and non-GML format !!! I guess 50% WFS servers must do it wrong
    1397             :          * anyway */
    1398             :         /* GeoServer does currently axis inversion for non GML output, but */
    1399             :         /* apparently this is not correct :
    1400             :          * http://jira.codehaus.org/browse/GEOS-3657 */
    1401         120 :         if (poGeom != nullptr && bAxisOrderAlreadyInverted &&
    1402          42 :             strcmp(poBaseDS->GetDriverName(), "GML") != 0)
    1403             :         {
    1404           9 :             poGeom->swapXY();
    1405             :         }
    1406             : 
    1407          78 :         if (poGeom && m_poSRS)
    1408          44 :             poGeom->assignSpatialReference(m_poSRS);
    1409          78 :         delete poSrcFeature;
    1410          78 :         return poNewFeature;
    1411          13 :     }
    1412             : }
    1413             : 
    1414             : /************************************************************************/
    1415             : /*                         ISetSpatialFilter()                          */
    1416             : /************************************************************************/
    1417             : 
    1418          30 : OGRErr OGRWFSLayer::ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom)
    1419             : {
    1420          30 :     if (bStreamingDS)
    1421             :     {
    1422           7 :         bReloadNeeded = true;
    1423             :     }
    1424          23 :     else if (poFetchedFilterGeom == nullptr && poBaseDS != nullptr)
    1425             :     {
    1426             :         /* If there was no filter set, and that we set one */
    1427             :         /* the new result set can only be a subset of the whole */
    1428             :         /* so no need to reload from source */
    1429           5 :         bReloadNeeded = false;
    1430             :     }
    1431          18 :     else if (poFetchedFilterGeom != nullptr && poGeom != nullptr &&
    1432           2 :              poBaseDS != nullptr)
    1433             :     {
    1434           1 :         OGREnvelope oOldEnvelope, oNewEnvelope;
    1435           1 :         poFetchedFilterGeom->getEnvelope(&oOldEnvelope);
    1436           1 :         poGeom->getEnvelope(&oNewEnvelope);
    1437             :         /* Optimization : we don't need to request the server */
    1438             :         /* if the new BBOX is inside the old BBOX as we have */
    1439             :         /* already all the features */
    1440           1 :         bReloadNeeded = !oOldEnvelope.Contains(oNewEnvelope);
    1441             :     }
    1442             :     else
    1443             :     {
    1444          17 :         bReloadNeeded = true;
    1445             :     }
    1446          30 :     nFeatures = -1;
    1447          30 :     const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeom);
    1448          30 :     ResetReading();
    1449          30 :     return eErr;
    1450             : }
    1451             : 
    1452             : /************************************************************************/
    1453             : /*                         SetAttributeFilter()                         */
    1454             : /************************************************************************/
    1455             : 
    1456          92 : OGRErr OGRWFSLayer::SetAttributeFilter(const char *pszFilter)
    1457             : {
    1458          92 :     if (pszFilter != nullptr && pszFilter[0] == 0)
    1459           8 :         pszFilter = nullptr;
    1460             : 
    1461         184 :     CPLString osOldWFSWhere(osWFSWhere);
    1462             : 
    1463          92 :     CPLFree(m_pszAttrQueryString);
    1464          92 :     m_pszAttrQueryString = (pszFilter) ? CPLStrdup(pszFilter) : nullptr;
    1465             : 
    1466          92 :     delete m_poAttrQuery;
    1467          92 :     m_poAttrQuery = nullptr;
    1468             : 
    1469          92 :     if (pszFilter != nullptr)
    1470             :     {
    1471          60 :         m_poAttrQuery = new OGRFeatureQuery();
    1472             : 
    1473          60 :         OGRErr eErr = m_poAttrQuery->Compile(GetLayerDefn(), pszFilter, TRUE,
    1474             :                                              WFSGetCustomFuncRegistrar());
    1475          60 :         if (eErr != OGRERR_NONE)
    1476             :         {
    1477          28 :             delete m_poAttrQuery;
    1478          28 :             m_poAttrQuery = nullptr;
    1479          28 :             return eErr;
    1480             :         }
    1481             :     }
    1482             : 
    1483          64 :     if (poDS->HasMinOperators() && m_poAttrQuery != nullptr)
    1484             :     {
    1485             :         swq_expr_node *poNode =
    1486          30 :             static_cast<swq_expr_node *>(m_poAttrQuery->GetSWQExpr());
    1487          30 :         poNode->ReplaceBetweenByGEAndLERecurse();
    1488          30 :         poNode->ReplaceInByOrRecurse();
    1489             : 
    1490          30 :         int bNeedsNullCheck = FALSE;
    1491          60 :         int nVersion = (strcmp(poDS->GetVersion(), "1.0.0") == 0) ? 100
    1492          30 :                        : (atoi(poDS->GetVersion()) >= 2)          ? 200
    1493          30 :                                                                   : 110;
    1494          30 :         if (poNode->field_type != SWQ_BOOLEAN)
    1495           0 :             osWFSWhere = "";
    1496             :         else
    1497          60 :             osWFSWhere = WFS_TurnSQLFilterToOGCFilter(
    1498             :                 poNode, nullptr, GetLayerDefn(), nVersion,
    1499          30 :                 poDS->PropertyIsNotEqualToSupported(),
    1500          30 :                 poDS->UseFeatureId() || bUseFeatureIdAtLayerLevel,
    1501          60 :                 poDS->DoesGmlObjectIdNeedGMLPrefix(), "", &bNeedsNullCheck);
    1502          30 :         if (bNeedsNullCheck && !poDS->HasNullCheck())
    1503           0 :             osWFSWhere = "";
    1504             :     }
    1505             :     else
    1506          34 :         osWFSWhere = "";
    1507             : 
    1508          64 :     if (m_poAttrQuery != nullptr && osWFSWhere.empty())
    1509             :     {
    1510           2 :         CPLDebug("WFS", "Using client-side only mode for filter \"%s\"",
    1511             :                  pszFilter);
    1512           2 :         OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
    1513           2 :         if (eErr != OGRERR_NONE)
    1514           0 :             return eErr;
    1515             :     }
    1516          64 :     ResetReading();
    1517             : 
    1518          64 :     osSQLWhere = (pszFilter) ? pszFilter : "";
    1519             : 
    1520          64 :     if (osWFSWhere != osOldWFSWhere)
    1521          46 :         bReloadNeeded = true;
    1522             :     else
    1523          18 :         bReloadNeeded = false;
    1524          64 :     nFeatures = -1;
    1525             : 
    1526          64 :     return OGRERR_NONE;
    1527             : }
    1528             : 
    1529             : /************************************************************************/
    1530             : /*                           TestCapability()                           */
    1531             : /************************************************************************/
    1532             : 
    1533         146 : int OGRWFSLayer::TestCapability(const char *pszCap) const
    1534             : 
    1535             : {
    1536         146 :     if (EQUAL(pszCap, OLCFastFeatureCount))
    1537             :     {
    1538           1 :         if (nFeatures >= 0)
    1539           0 :             return TRUE;
    1540             : 
    1541           1 :         return poBaseLayer != nullptr && m_poFilterGeom == nullptr &&
    1542           1 :                m_poAttrQuery == nullptr &&
    1543           2 :                poBaseLayer->TestCapability(pszCap) &&
    1544           0 :                (!poDS->IsPagingAllowed() &&
    1545           1 :                 poBaseLayer->GetFeatureCount() < poDS->GetPageSize());
    1546             :     }
    1547             : 
    1548         145 :     else if (EQUAL(pszCap, OLCFastGetExtent))
    1549             :     {
    1550           7 :         if (m_oExtents.IsInit())
    1551           0 :             return TRUE;
    1552             : 
    1553           7 :         return poBaseLayer != nullptr && poBaseLayer->TestCapability(pszCap);
    1554             :     }
    1555             : 
    1556         138 :     else if (EQUAL(pszCap, OLCStringsAsUTF8))
    1557           2 :         return poBaseLayer != nullptr && poBaseLayer->TestCapability(pszCap);
    1558             : 
    1559         136 :     else if (EQUAL(pszCap, OLCSequentialWrite) ||
    1560          98 :              EQUAL(pszCap, OLCDeleteFeature) || EQUAL(pszCap, OLCRandomWrite))
    1561             :     {
    1562          78 :         GetLayerDefn();
    1563         156 :         return poDS->SupportTransactions() && poDS->UpdateMode() &&
    1564         156 :                poFeatureDefn->GetFieldIndex("gml_id") == 0;
    1565             :     }
    1566          58 :     else if (EQUAL(pszCap, OLCTransactions))
    1567             :     {
    1568          50 :         return poDS->SupportTransactions() && poDS->UpdateMode();
    1569             :     }
    1570           8 :     else if (EQUAL(pszCap, OLCIgnoreFields))
    1571             :     {
    1572           0 :         return TRUE;
    1573             :     }
    1574             : 
    1575           8 :     return FALSE;
    1576             : }
    1577             : 
    1578             : /************************************************************************/
    1579             : /*                  ExecuteGetFeatureResultTypeHits()                   */
    1580             : /************************************************************************/
    1581             : 
    1582           9 : GIntBig OGRWFSLayer::ExecuteGetFeatureResultTypeHits()
    1583             : {
    1584           9 :     char *pabyData = nullptr;
    1585          18 :     CPLString osURL = MakeGetFeatureURL(0, TRUE);
    1586           9 :     if (pszRequiredOutputFormat)
    1587           0 :         osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
    1588           0 :                              WFS_EscapeURL(pszRequiredOutputFormat));
    1589           9 :     CPLDebug("WFS", "%s", osURL.c_str());
    1590             : 
    1591           9 :     CPLHTTPResult *psResult = poDS->HTTPFetch(osURL, nullptr);
    1592           9 :     if (psResult == nullptr)
    1593             :     {
    1594           1 :         return -1;
    1595             :     }
    1596             : 
    1597             :     /* http://demo.snowflakesoftware.com:8080/Obstacle_AIXM_ZIP/GOPublisherWFS
    1598             :      * returns */
    1599             :     /* zip content, including for RESULTTYPE=hits */
    1600           8 :     if (psResult->pszContentType != nullptr &&
    1601           0 :         strstr(psResult->pszContentType, "application/zip") != nullptr)
    1602             :     {
    1603             :         const CPLString osTmpFileName(
    1604           0 :             VSIMemGenerateHiddenFilename("wfstemphits.zip"));
    1605           0 :         VSILFILE *fp = VSIFileFromMemBuffer(osTmpFileName, psResult->pabyData,
    1606           0 :                                             psResult->nDataLen, FALSE);
    1607           0 :         VSIFCloseL(fp);
    1608             : 
    1609           0 :         CPLString osZipTmpFileName("/vsizip/" + osTmpFileName);
    1610             : 
    1611           0 :         char **papszDirContent = VSIReadDir(osZipTmpFileName);
    1612           0 :         if (CSLCount(papszDirContent) != 1)
    1613             :         {
    1614           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1615             :                      "Cannot parse result of RESULTTYPE=hits request : more "
    1616             :                      "than one file in zip");
    1617           0 :             CSLDestroy(papszDirContent);
    1618           0 :             CPLHTTPDestroyResult(psResult);
    1619           0 :             VSIUnlink(osTmpFileName);
    1620           0 :             return -1;
    1621             :         }
    1622             : 
    1623           0 :         CPLString osFileInZipTmpFileName = osZipTmpFileName + "/";
    1624           0 :         osFileInZipTmpFileName += papszDirContent[0];
    1625             : 
    1626           0 :         fp = VSIFOpenL(osFileInZipTmpFileName.c_str(), "rb");
    1627             :         VSIStatBufL sBuf;
    1628           0 :         if (fp == nullptr ||
    1629           0 :             VSIStatL(osFileInZipTmpFileName.c_str(), &sBuf) != 0)
    1630             :         {
    1631           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1632             :                      "Cannot parse result of RESULTTYPE=hits request : cannot "
    1633             :                      "open one file in zip");
    1634           0 :             CSLDestroy(papszDirContent);
    1635           0 :             CPLHTTPDestroyResult(psResult);
    1636           0 :             VSIUnlink(osTmpFileName);
    1637           0 :             if (fp)
    1638           0 :                 VSIFCloseL(fp);
    1639           0 :             return -1;
    1640             :         }
    1641             :         pabyData = static_cast<char *>(
    1642           0 :             CPLMalloc(static_cast<size_t>(sBuf.st_size + 1)));
    1643           0 :         pabyData[sBuf.st_size] = 0;
    1644           0 :         VSIFReadL(pabyData, 1, static_cast<size_t>(sBuf.st_size), fp);
    1645           0 :         VSIFCloseL(fp);
    1646             : 
    1647           0 :         CSLDestroy(papszDirContent);
    1648           0 :         VSIUnlink(osTmpFileName);
    1649             :     }
    1650             :     else
    1651             :     {
    1652           8 :         pabyData = reinterpret_cast<char *>(psResult->pabyData);
    1653           8 :         psResult->pabyData = nullptr;
    1654             :     }
    1655             : 
    1656           8 :     if (strstr(pabyData, "<ServiceExceptionReport") != nullptr ||
    1657           7 :         strstr(pabyData, "<ows:ExceptionReport") != nullptr)
    1658             :     {
    1659           1 :         if (poDS->IsOldDeegree(pabyData))
    1660             :         {
    1661           0 :             CPLHTTPDestroyResult(psResult);
    1662           0 :             return ExecuteGetFeatureResultTypeHits();
    1663             :         }
    1664           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
    1665             :                  pabyData);
    1666           1 :         CPLHTTPDestroyResult(psResult);
    1667           1 :         CPLFree(pabyData);
    1668           1 :         return -1;
    1669             :     }
    1670             : 
    1671           7 :     CPLXMLNode *psXML = CPLParseXMLString(pabyData);
    1672           7 :     if (psXML == nullptr)
    1673             :     {
    1674           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    1675             :                  pabyData);
    1676           2 :         CPLHTTPDestroyResult(psResult);
    1677           2 :         CPLFree(pabyData);
    1678           2 :         return -1;
    1679             :     }
    1680             : 
    1681           5 :     CPLStripXMLNamespace(psXML, nullptr, TRUE);
    1682           5 :     CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=FeatureCollection");
    1683           5 :     if (psRoot == nullptr)
    1684             :     {
    1685           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1686             :                  "Cannot find <FeatureCollection>");
    1687           1 :         CPLDestroyXMLNode(psXML);
    1688           1 :         CPLHTTPDestroyResult(psResult);
    1689           1 :         CPLFree(pabyData);
    1690           1 :         return -1;
    1691             :     }
    1692             : 
    1693           4 :     const char *pszValue = CPLGetXMLValue(psRoot, "numberOfFeatures", nullptr);
    1694           4 :     if (pszValue == nullptr)
    1695             :         pszValue =
    1696           3 :             CPLGetXMLValue(psRoot, "numberMatched", nullptr); /* WFS 2.0.0 */
    1697           4 :     if (pszValue == nullptr)
    1698             :     {
    1699           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find numberOfFeatures");
    1700           1 :         CPLDestroyXMLNode(psXML);
    1701           1 :         CPLHTTPDestroyResult(psResult);
    1702           1 :         CPLFree(pabyData);
    1703             : 
    1704           1 :         poDS->DisableSupportHits();
    1705           1 :         return -1;
    1706             :     }
    1707             : 
    1708           3 :     GIntBig l_nFeatures = CPLAtoGIntBig(pszValue);
    1709             :     /* Hum,
    1710             :      * http://deegree3-testing.deegree.org:80/deegree-inspire-node/services?MAXFEATURES=10&SERVICE=WFS&VERSION=1.1.0&REQUEST=GetFeature&TYPENAME=ad:Address&OUTPUTFORMAT=text/xml;%20subtype=gml/3.2.1&RESULTTYPE=hits
    1711             :      */
    1712             :     /* returns more than MAXFEATURES features... So truncate to MAXFEATURES */
    1713             :     CPLString osMaxFeatures = CPLURLGetValue(
    1714           3 :         osURL, atoi(poDS->GetVersion()) >= 2 ? "COUNT" : "MAXFEATURES");
    1715           3 :     if (!osMaxFeatures.empty())
    1716             :     {
    1717           0 :         GIntBig nMaxFeatures = CPLAtoGIntBig(osMaxFeatures);
    1718           0 :         if (l_nFeatures > nMaxFeatures)
    1719             :         {
    1720           0 :             CPLDebug("WFS",
    1721             :                      "Truncating result from " CPL_FRMT_GIB " to " CPL_FRMT_GIB,
    1722             :                      l_nFeatures, nMaxFeatures);
    1723           0 :             l_nFeatures = nMaxFeatures;
    1724             :         }
    1725             :     }
    1726             : 
    1727           3 :     CPLDestroyXMLNode(psXML);
    1728           3 :     CPLHTTPDestroyResult(psResult);
    1729           3 :     CPLFree(pabyData);
    1730             : 
    1731           3 :     return l_nFeatures;
    1732             : }
    1733             : 
    1734             : /************************************************************************/
    1735             : /*             CanRunGetFeatureCountAndGetExtentTogether()              */
    1736             : /************************************************************************/
    1737             : 
    1738          16 : int OGRWFSLayer::CanRunGetFeatureCountAndGetExtentTogether()
    1739             : {
    1740             :     /* In some cases, we can evaluate the result of GetFeatureCount() */
    1741             :     /* and GetExtent() with the same data */
    1742          16 :     CPLString osRequestURL = MakeGetFeatureURL(0, FALSE);
    1743          26 :     return (!m_oExtents.IsInit() && nFeatures < 0 &&
    1744          10 :             osRequestURL.ifind("FILTER") == std::string::npos &&
    1745          10 :             osRequestURL.ifind("MAXFEATURES") == std::string::npos &&
    1746          34 :             osRequestURL.ifind("COUNT") == std::string::npos &&
    1747          40 :             !(GetLayerDefn()->IsGeometryIgnored()));
    1748             : }
    1749             : 
    1750             : /************************************************************************/
    1751             : /*                          GetFeatureCount()                           */
    1752             : /************************************************************************/
    1753             : 
    1754          14 : GIntBig OGRWFSLayer::GetFeatureCount(int bForce)
    1755             : {
    1756          14 :     if (nFeatures >= 0)
    1757           2 :         return nFeatures;
    1758             : 
    1759          12 :     if (poBaseLayer && TestCapability(OLCFastFeatureCount))
    1760           0 :         return poBaseLayer->GetFeatureCount(bForce);
    1761             : 
    1762          22 :     if ((m_poAttrQuery == nullptr || !osWFSWhere.empty()) &&
    1763          10 :         poDS->GetFeatureSupportHits())
    1764             :     {
    1765           9 :         nFeatures = ExecuteGetFeatureResultTypeHits();
    1766           9 :         if (nFeatures >= 0)
    1767           3 :             return nFeatures;
    1768             :     }
    1769             : 
    1770             :     /* If we have not yet the base layer, try to read one */
    1771             :     /* feature, and then query again OLCFastFeatureCount on the */
    1772             :     /* base layer. In case the WFS response would contain the */
    1773             :     /* number of features */
    1774          17 :     if (poBaseLayer == nullptr &&
    1775           8 :         (m_poAttrQuery == nullptr || !osWFSWhere.empty()))
    1776             :     {
    1777           7 :         ResetReading();
    1778           7 :         OGRFeature *poFeature = GetNextFeature();
    1779           7 :         delete poFeature;
    1780           7 :         ResetReading();
    1781             : 
    1782           7 :         if (nFeatures >= 0)
    1783           0 :             return nFeatures;
    1784             : 
    1785           7 :         if (poBaseLayer && TestCapability(OLCFastFeatureCount))
    1786           0 :             return poBaseLayer->GetFeatureCount(bForce);
    1787             :     }
    1788             : 
    1789             :     /* In some cases, we can evaluate the result of GetFeatureCount() */
    1790             :     /* and GetExtent() with the same data */
    1791           9 :     if (CanRunGetFeatureCountAndGetExtentTogether())
    1792             :     {
    1793           1 :         OGREnvelope sDummy;
    1794           1 :         CPL_IGNORE_RET_VAL(GetExtent(&sDummy));
    1795             :     }
    1796             : 
    1797           9 :     if (nFeatures < 0)
    1798           9 :         nFeatures = OGRLayer::GetFeatureCount(bForce);
    1799             : 
    1800           9 :     return nFeatures;
    1801             : }
    1802             : 
    1803             : /************************************************************************/
    1804             : /*                             SetExtents()                             */
    1805             : /************************************************************************/
    1806             : 
    1807         152 : void OGRWFSLayer::SetExtents(double dfMinXIn, double dfMinYIn, double dfMaxXIn,
    1808             :                              double dfMaxYIn)
    1809             : {
    1810         152 :     m_oExtents.MinX = dfMinXIn;
    1811         152 :     m_oExtents.MinY = dfMinYIn;
    1812         152 :     m_oExtents.MaxX = dfMaxXIn;
    1813         152 :     m_oExtents.MaxY = dfMaxYIn;
    1814         152 : }
    1815             : 
    1816             : /************************************************************************/
    1817             : /*                          SetWGS84Extents()                           */
    1818             : /************************************************************************/
    1819             : 
    1820         152 : void OGRWFSLayer::SetWGS84Extents(double dfMinXIn, double dfMinYIn,
    1821             :                                   double dfMaxXIn, double dfMaxYIn)
    1822             : {
    1823         152 :     m_oWGS84Extents.MinX = dfMinXIn;
    1824         152 :     m_oWGS84Extents.MinY = dfMinYIn;
    1825         152 :     m_oWGS84Extents.MaxX = dfMaxXIn;
    1826         152 :     m_oWGS84Extents.MaxY = dfMaxYIn;
    1827         152 : }
    1828             : 
    1829             : /************************************************************************/
    1830             : /*                             IGetExtent()                             */
    1831             : /************************************************************************/
    1832             : 
    1833          16 : OGRErr OGRWFSLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
    1834             :                                bool bForce)
    1835             : {
    1836          16 :     if (m_oExtents.IsInit())
    1837             :     {
    1838           9 :         *psExtent = m_oExtents;
    1839           9 :         return OGRERR_NONE;
    1840             :     }
    1841             : 
    1842             :     /* If we have not yet the base layer, try to read one */
    1843             :     /* feature, and then query again OLCFastGetExtent on the */
    1844             :     /* base layer. In case the WFS response would contain the */
    1845             :     /* global extent */
    1846           7 :     if (poBaseLayer == nullptr)
    1847             :     {
    1848           7 :         ResetReading();
    1849           7 :         OGRFeature *poFeature = GetNextFeature();
    1850           7 :         delete poFeature;
    1851           7 :         ResetReading();
    1852             :     }
    1853             : 
    1854           7 :     if (TestCapability(OLCFastGetExtent))
    1855           0 :         return poBaseLayer->GetExtent(iGeomField, psExtent, bForce);
    1856             : 
    1857             :     /* In some cases, we can evaluate the result of GetFeatureCount() */
    1858             :     /* and GetExtent() with the same data */
    1859           7 :     if (CanRunGetFeatureCountAndGetExtentTogether())
    1860             :     {
    1861           7 :         bCountFeaturesInGetNextFeature = true;
    1862           7 :         nFeatures = 0;
    1863             :     }
    1864             : 
    1865           7 :     OGRErr eErr = OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
    1866             : 
    1867           7 :     if (bCountFeaturesInGetNextFeature)
    1868             :     {
    1869           7 :         if (eErr == OGRERR_NONE)
    1870             :         {
    1871           2 :             m_oExtents = *psExtent;
    1872             :         }
    1873             :         else
    1874             :         {
    1875           5 :             nFeatures = -1;
    1876             :         }
    1877           7 :         bCountFeaturesInGetNextFeature = false;
    1878             :     }
    1879             : 
    1880           7 :     return eErr;
    1881             : }
    1882             : 
    1883             : /************************************************************************/
    1884             : /*                            GetShortName()                            */
    1885             : /************************************************************************/
    1886             : 
    1887         718 : const char *OGRWFSLayer::GetShortName()
    1888             : {
    1889         718 :     const char *pszShortName = strchr(pszName, ':');
    1890         718 :     if (pszShortName == nullptr)
    1891         622 :         pszShortName = pszName;
    1892             :     else
    1893          96 :         pszShortName++;
    1894         718 :     return pszShortName;
    1895             : }
    1896             : 
    1897             : /************************************************************************/
    1898             : /*                           GetPostHeader()                            */
    1899             : /************************************************************************/
    1900             : 
    1901          62 : CPLString OGRWFSLayer::GetPostHeader()
    1902             : {
    1903          62 :     CPLString osPost;
    1904          62 :     osPost += "<?xml version=\"1.0\"?>\n";
    1905          62 :     osPost += "<wfs:Transaction xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
    1906             :     osPost += "                 "
    1907          62 :               "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
    1908          62 :     osPost += "                 service=\"WFS\" version=\"";
    1909          62 :     osPost += poDS->GetVersion();
    1910          62 :     osPost += "\"\n";
    1911          62 :     osPost += "                 xmlns:gml=\"http://www.opengis.net/gml\"\n";
    1912          62 :     osPost += "                 xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
    1913             :     osPost +=
    1914             :         "                 xsi:schemaLocation=\"http://www.opengis.net/wfs "
    1915          62 :         "http://schemas.opengis.net/wfs/";
    1916          62 :     osPost += poDS->GetVersion();
    1917          62 :     osPost += "/wfs.xsd ";
    1918          62 :     osPost += osTargetNamespace;
    1919          62 :     osPost += " ";
    1920             : 
    1921             :     char *pszXMLEncoded =
    1922          62 :         CPLEscapeString(GetDescribeFeatureTypeURL(FALSE), -1, CPLES_XML);
    1923          62 :     osPost += pszXMLEncoded;
    1924          62 :     CPLFree(pszXMLEncoded);
    1925             : 
    1926          62 :     osPost += "\">\n";
    1927             : 
    1928          62 :     return osPost;
    1929             : }
    1930             : 
    1931             : /************************************************************************/
    1932             : /*                      SerializeFieldValueAsXML()                      */
    1933             : /************************************************************************/
    1934             : 
    1935          20 : static std::string SerializeFieldValueAsXML(const OGRFeature *poFeature, int i,
    1936             :                                             const OGRFieldDefn *poFDefn)
    1937             : {
    1938          20 :     switch (poFDefn->GetType())
    1939             :     {
    1940           6 :         case OFTInteger:
    1941             :         {
    1942           6 :             if (poFDefn->GetSubType() == OFSTBoolean)
    1943           2 :                 return poFeature->GetFieldAsInteger(i) ? "true" : "false";
    1944             :             else
    1945           4 :                 return CPLSPrintf("%d", poFeature->GetFieldAsInteger(i));
    1946             :         }
    1947             : 
    1948           2 :         case OFTInteger64:
    1949           2 :             return CPLSPrintf(CPL_FRMT_GIB, poFeature->GetFieldAsInteger64(i));
    1950             : 
    1951           4 :         case OFTReal:
    1952           4 :             return CPLSPrintf("%.17g", poFeature->GetFieldAsDouble(i));
    1953             : 
    1954           2 :         case OFTDateTime:
    1955           2 :             return poFeature->GetFieldAsISO8601DateTime(i, nullptr);
    1956             : 
    1957           2 :         case OFTDate:
    1958             :         {
    1959           2 :             const auto poRawValue = poFeature->GetRawFieldRef(i);
    1960           2 :             return CPLSPrintf("%04d-%02d-%02d", poRawValue->Date.Year,
    1961           2 :                               poRawValue->Date.Month, poRawValue->Date.Day);
    1962             :         }
    1963             : 
    1964           4 :         case OFTString:
    1965             :         case OFTIntegerList:
    1966             :         case OFTInteger64List:
    1967             :         case OFTRealList:
    1968             :         case OFTStringList:
    1969             :         case OFTWideString:
    1970             :         case OFTWideStringList:
    1971             :         case OFTBinary:
    1972             :         case OFTTime:
    1973           4 :             break;
    1974             :     }
    1975             : 
    1976             :     char *pszXMLEncoded =
    1977           4 :         CPLEscapeString(poFeature->GetFieldAsString(i), -1, CPLES_XML);
    1978           8 :     std::string ret(pszXMLEncoded);
    1979           4 :     CPLFree(pszXMLEncoded);
    1980           4 :     return ret;
    1981             : }
    1982             : 
    1983             : /************************************************************************/
    1984             : /*                           ICreateFeature()                           */
    1985             : /************************************************************************/
    1986             : 
    1987          38 : OGRErr OGRWFSLayer::ICreateFeature(OGRFeature *poFeature)
    1988             : {
    1989          38 :     if (!TestCapability(OLCSequentialWrite))
    1990             :     {
    1991           0 :         if (!poDS->SupportTransactions())
    1992           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1993             :                      "CreateFeature() not supported: no WMS-T features "
    1994             :                      "advertized by server");
    1995           0 :         else if (!poDS->UpdateMode())
    1996           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1997             :                      "CreateFeature() not supported: datasource opened as "
    1998             :                      "read-only");
    1999           0 :         return OGRERR_FAILURE;
    2000             :     }
    2001             : 
    2002          38 :     if (poGMLFeatureClass == nullptr)
    2003             :     {
    2004           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2005             :                  "Cannot insert feature because we didn't manage to parse the "
    2006             :                  ".XSD schema");
    2007           0 :         return OGRERR_FAILURE;
    2008             :     }
    2009             : 
    2010          38 :     if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
    2011             :     {
    2012           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find gml_id field");
    2013           0 :         return OGRERR_FAILURE;
    2014             :     }
    2015             : 
    2016          38 :     if (poFeature->IsFieldSetAndNotNull(0))
    2017             :     {
    2018           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2019             :                  "Cannot insert a feature when gml_id field is already set");
    2020           2 :         return OGRERR_FAILURE;
    2021             :     }
    2022             : 
    2023          72 :     CPLString osPost;
    2024             : 
    2025          36 :     const char *pszShortName = GetShortName();
    2026             : 
    2027          36 :     if (!bInTransaction)
    2028             :     {
    2029          20 :         osPost += GetPostHeader();
    2030          20 :         osPost += "  <wfs:Insert>\n";
    2031             :     }
    2032          36 :     osPost += "    <feature:";
    2033          36 :     osPost += pszShortName;
    2034          36 :     osPost += " xmlns:feature=\"";
    2035          36 :     osPost += osTargetNamespace;
    2036          36 :     osPost += "\">\n";
    2037             : 
    2038         360 :     for (int i = 1; i <= poFeature->GetFieldCount(); i++)
    2039             :     {
    2040         720 :         if (poGMLFeatureClass->GetGeometryPropertyCount() == 1 &&
    2041         360 :             poGMLFeatureClass->GetGeometryProperty(0)->GetAttributeIndex() ==
    2042         360 :                 i - 1)
    2043             :         {
    2044          36 :             OGRGeometry *poGeom = poFeature->GetGeometryRef();
    2045          36 :             if (poGeom != nullptr && !osGeometryColumnName.empty())
    2046             :             {
    2047           2 :                 if (poGeom->getSpatialReference() == nullptr)
    2048           2 :                     poGeom->assignSpatialReference(m_poSRS);
    2049           2 :                 char *pszGML = nullptr;
    2050           2 :                 if (strcmp(poDS->GetVersion(), "1.1.0") == 0 ||
    2051           0 :                     atoi(poDS->GetVersion()) >= 2)
    2052             :                 {
    2053           2 :                     char **papszOptions = CSLAddString(nullptr, "FORMAT=GML3");
    2054           2 :                     pszGML = OGR_G_ExportToGMLEx(OGRGeometry::ToHandle(poGeom),
    2055             :                                                  papszOptions);
    2056           2 :                     CSLDestroy(papszOptions);
    2057             :                 }
    2058             :                 else
    2059           0 :                     pszGML = OGR_G_ExportToGML(OGRGeometry::ToHandle(poGeom));
    2060           2 :                 osPost += "      <feature:";
    2061           2 :                 osPost += osGeometryColumnName;
    2062           2 :                 osPost += ">";
    2063           2 :                 osPost += pszGML;
    2064           2 :                 osPost += "</feature:";
    2065           2 :                 osPost += osGeometryColumnName;
    2066           2 :                 osPost += ">\n";
    2067           2 :                 CPLFree(pszGML);
    2068             :             }
    2069             :         }
    2070         360 :         if (i == poFeature->GetFieldCount())
    2071          36 :             break;
    2072             : 
    2073             : #ifdef notdef
    2074             :         if (poFeature->IsFieldNull(i))
    2075             :         {
    2076             :             const OGRFieldDefn *poFDefn = poFeature->GetFieldDefnRef(i);
    2077             :             osPost += "      <feature:";
    2078             :             osPost += poFDefn->GetNameRef();
    2079             :             osPost += " xsi:nil=\"true\" />\n";
    2080             :         }
    2081             :         else
    2082             : #endif
    2083         324 :             if (poFeature->IsFieldSet(i) && !poFeature->IsFieldNull(i))
    2084             :         {
    2085          14 :             const OGRFieldDefn *poFDefn = poFeature->GetFieldDefnRef(i);
    2086          14 :             osPost += "      <feature:";
    2087          14 :             osPost += poFDefn->GetNameRef();
    2088          14 :             osPost += ">";
    2089          14 :             osPost += SerializeFieldValueAsXML(poFeature, i, poFDefn);
    2090          14 :             osPost += "</feature:";
    2091          14 :             osPost += poFDefn->GetNameRef();
    2092          14 :             osPost += ">\n";
    2093             :         }
    2094             :     }
    2095             : 
    2096          36 :     osPost += "    </feature:";
    2097          36 :     osPost += pszShortName;
    2098          36 :     osPost += ">\n";
    2099             : 
    2100          36 :     if (!bInTransaction)
    2101             :     {
    2102          20 :         osPost += "  </wfs:Insert>\n";
    2103          20 :         osPost += "</wfs:Transaction>\n";
    2104             :     }
    2105             :     else
    2106             :     {
    2107          16 :         osGlobalInsert += osPost;
    2108          16 :         nExpectedInserts++;
    2109          16 :         return OGRERR_NONE;
    2110             :     }
    2111             : 
    2112          20 :     CPLDebug("WFS", "Post : %s", osPost.c_str());
    2113             : 
    2114          20 :     char **papszOptions = nullptr;
    2115          20 :     papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    2116             :     papszOptions =
    2117          20 :         CSLAddNameValue(papszOptions, "HEADERS",
    2118             :                         "Content-Type: application/xml; charset=UTF-8");
    2119             : 
    2120             :     CPLHTTPResult *psResult =
    2121          20 :         poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
    2122          20 :     CSLDestroy(papszOptions);
    2123             : 
    2124          20 :     if (psResult == nullptr)
    2125             :     {
    2126           6 :         return OGRERR_FAILURE;
    2127             :     }
    2128             : 
    2129          14 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2130          12 :                "<ServiceExceptionReport") != nullptr ||
    2131          12 :         strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2132             :                "<ows:ExceptionReport") != nullptr)
    2133             :     {
    2134           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
    2135             :                  psResult->pabyData);
    2136           2 :         CPLHTTPDestroyResult(psResult);
    2137           2 :         return OGRERR_FAILURE;
    2138             :     }
    2139             : 
    2140          12 :     CPLDebug("WFS", "Response: %s", psResult->pabyData);
    2141             : 
    2142             :     CPLXMLNode *psXML =
    2143          12 :         CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
    2144          12 :     if (psXML == nullptr)
    2145             :     {
    2146           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    2147             :                  psResult->pabyData);
    2148           2 :         CPLHTTPDestroyResult(psResult);
    2149           2 :         return OGRERR_FAILURE;
    2150             :     }
    2151             : 
    2152          10 :     CPLStripXMLNamespace(psXML, nullptr, TRUE);
    2153          10 :     bool bUse100Schema = false;
    2154          10 :     CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TransactionResponse");
    2155          10 :     if (psRoot == nullptr)
    2156             :     {
    2157           2 :         psRoot = CPLGetXMLNode(psXML, "=WFS_TransactionResponse");
    2158           2 :         if (psRoot)
    2159           0 :             bUse100Schema = true;
    2160             :     }
    2161             : 
    2162          10 :     if (psRoot == nullptr)
    2163             :     {
    2164           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2165             :                  "Cannot find <TransactionResponse>");
    2166           2 :         CPLDestroyXMLNode(psXML);
    2167           2 :         CPLHTTPDestroyResult(psResult);
    2168           2 :         return OGRERR_FAILURE;
    2169             :     }
    2170             : 
    2171           8 :     CPLXMLNode *psFeatureID = nullptr;
    2172             : 
    2173           8 :     if (bUse100Schema)
    2174             :     {
    2175           0 :         if (CPLGetXMLNode(psRoot, "TransactionResult.Status.FAILED"))
    2176             :         {
    2177           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Insert failed : %s",
    2178             :                      psResult->pabyData);
    2179           0 :             CPLDestroyXMLNode(psXML);
    2180           0 :             CPLHTTPDestroyResult(psResult);
    2181           0 :             return OGRERR_FAILURE;
    2182             :         }
    2183             : 
    2184           0 :         psFeatureID = CPLGetXMLNode(psRoot, "InsertResult.FeatureId");
    2185           0 :         if (psFeatureID == nullptr)
    2186             :         {
    2187           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2188             :                      "Cannot find InsertResult.FeatureId");
    2189           0 :             CPLDestroyXMLNode(psXML);
    2190           0 :             CPLHTTPDestroyResult(psResult);
    2191           0 :             return OGRERR_FAILURE;
    2192             :         }
    2193             :     }
    2194             :     else
    2195             :     {
    2196           8 :         const char *pszFeatureIdElt = atoi(poDS->GetVersion()) >= 2
    2197           8 :                                           ? "InsertResults.Feature.ResourceId"
    2198           8 :                                           : "InsertResults.Feature.FeatureId";
    2199           8 :         psFeatureID = CPLGetXMLNode(psRoot, pszFeatureIdElt);
    2200           8 :         if (psFeatureID == nullptr)
    2201             :         {
    2202           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
    2203             :                      pszFeatureIdElt);
    2204           2 :             CPLDestroyXMLNode(psXML);
    2205           2 :             CPLHTTPDestroyResult(psResult);
    2206           2 :             return OGRERR_FAILURE;
    2207             :         }
    2208             :     }
    2209             : 
    2210           6 :     const char *pszFIDAttr = atoi(poDS->GetVersion()) >= 2 ? "rid" : "fid";
    2211           6 :     const char *pszFID = CPLGetXMLValue(psFeatureID, pszFIDAttr, nullptr);
    2212           6 :     if (pszFID == nullptr)
    2213             :     {
    2214           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszFIDAttr);
    2215           2 :         CPLDestroyXMLNode(psXML);
    2216           2 :         CPLHTTPDestroyResult(psResult);
    2217           2 :         return OGRERR_FAILURE;
    2218             :     }
    2219             : 
    2220           4 :     poFeature->SetField("gml_id", pszFID);
    2221             : 
    2222             :     /* If the returned fid is of the form layer_name.num, then use */
    2223             :     /* num as the OGR FID */
    2224           4 :     if (strncmp(pszFID, pszShortName, strlen(pszShortName)) == 0 &&
    2225           4 :         pszFID[strlen(pszShortName)] == '.')
    2226             :     {
    2227           4 :         GIntBig nFID = CPLAtoGIntBig(pszFID + strlen(pszShortName) + 1);
    2228           4 :         poFeature->SetFID(nFID);
    2229             :     }
    2230             : 
    2231           4 :     CPLDebug("WFS", "Got FID = " CPL_FRMT_GIB, poFeature->GetFID());
    2232             : 
    2233           4 :     CPLDestroyXMLNode(psXML);
    2234           4 :     CPLHTTPDestroyResult(psResult);
    2235             : 
    2236             :     /* Invalidate layer */
    2237           4 :     bReloadNeeded = true;
    2238           4 :     nFeatures = -1;
    2239           4 :     m_oExtents = OGREnvelope();
    2240             : 
    2241           4 :     return OGRERR_NONE;
    2242             : }
    2243             : 
    2244             : /************************************************************************/
    2245             : /*                            ISetFeature()                             */
    2246             : /************************************************************************/
    2247             : 
    2248          16 : OGRErr OGRWFSLayer::ISetFeature(OGRFeature *poFeature)
    2249             : {
    2250          16 :     if (!TestCapability(OLCRandomWrite))
    2251             :     {
    2252           0 :         if (!poDS->SupportTransactions())
    2253           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2254             :                      "SetFeature() not supported: no WMS-T features advertized "
    2255             :                      "by server");
    2256           0 :         else if (!poDS->UpdateMode())
    2257           0 :             CPLError(
    2258             :                 CE_Failure, CPLE_AppDefined,
    2259             :                 "SetFeature() not supported: datasource opened as read-only");
    2260           0 :         return OGRERR_FAILURE;
    2261             :     }
    2262             : 
    2263          16 :     if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
    2264             :     {
    2265           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find gml_id field");
    2266           0 :         return OGRERR_FAILURE;
    2267             :     }
    2268             : 
    2269          16 :     if (poFeature->IsFieldSetAndNotNull(0) == FALSE)
    2270             :     {
    2271           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2272             :                  "Cannot update a feature when gml_id field is not set");
    2273           2 :         return OGRERR_FAILURE;
    2274             :     }
    2275             : 
    2276          14 :     if (bInTransaction)
    2277             :     {
    2278           0 :         CPLError(
    2279             :             CE_Warning, CPLE_AppDefined,
    2280             :             "SetFeature() not yet dealt in transaction. Issued immediately");
    2281             :     }
    2282             : 
    2283          14 :     const char *pszShortName = GetShortName();
    2284             : 
    2285          28 :     CPLString osPost;
    2286          14 :     osPost += GetPostHeader();
    2287             : 
    2288          14 :     osPost += "  <wfs:Update typeName=\"feature:";
    2289          14 :     osPost += pszShortName;
    2290          14 :     osPost += "\" xmlns:feature=\"";
    2291          14 :     osPost += osTargetNamespace;
    2292          14 :     osPost += "\">\n";
    2293             : 
    2294          14 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    2295          14 :     if (!osGeometryColumnName.empty())
    2296             :     {
    2297          14 :         osPost += "    <wfs:Property>\n";
    2298          14 :         osPost += "      <wfs:Name>";
    2299          14 :         osPost += osGeometryColumnName;
    2300          14 :         osPost += "</wfs:Name>\n";
    2301          14 :         if (poGeom != nullptr)
    2302             :         {
    2303           2 :             if (poGeom->getSpatialReference() == nullptr)
    2304           2 :                 poGeom->assignSpatialReference(m_poSRS);
    2305           2 :             char *pszGML = nullptr;
    2306           2 :             if (strcmp(poDS->GetVersion(), "1.1.0") == 0 ||
    2307           0 :                 atoi(poDS->GetVersion()) >= 2)
    2308             :             {
    2309           2 :                 char **papszOptions = CSLAddString(nullptr, "FORMAT=GML3");
    2310           2 :                 pszGML = OGR_G_ExportToGMLEx(OGRGeometry::ToHandle(poGeom),
    2311             :                                              papszOptions);
    2312           2 :                 CSLDestroy(papszOptions);
    2313             :             }
    2314             :             else
    2315           0 :                 pszGML = OGR_G_ExportToGML(OGRGeometry::ToHandle(poGeom));
    2316           2 :             osPost += "      <wfs:Value>";
    2317           2 :             osPost += pszGML;
    2318           2 :             osPost += "</wfs:Value>\n";
    2319           2 :             CPLFree(pszGML);
    2320             :         }
    2321          14 :         osPost += "    </wfs:Property>\n";
    2322             :     }
    2323             : 
    2324         140 :     for (int i = 1; i < poFeature->GetFieldCount(); i++)
    2325             :     {
    2326         126 :         const OGRFieldDefn *poFDefn = poFeature->GetFieldDefnRef(i);
    2327             : 
    2328         126 :         osPost += "    <wfs:Property>\n";
    2329         126 :         osPost += "      <wfs:Name>";
    2330         126 :         osPost += poFDefn->GetNameRef();
    2331         126 :         osPost += "</wfs:Name>\n";
    2332         126 :         if (poFeature->IsFieldSetAndNotNull(i))
    2333             :         {
    2334           6 :             osPost += "      <wfs:Value>";
    2335           6 :             osPost += SerializeFieldValueAsXML(poFeature, i, poFDefn);
    2336           6 :             osPost += "</wfs:Value>\n";
    2337             :         }
    2338         126 :         osPost += "    </wfs:Property>\n";
    2339             :     }
    2340          14 :     osPost += "    <ogc:Filter>\n";
    2341          14 :     if (poDS->UseFeatureId() || bUseFeatureIdAtLayerLevel)
    2342           0 :         osPost += "      <ogc:FeatureId fid=\"";
    2343          14 :     else if (atoi(poDS->GetVersion()) >= 2)
    2344           0 :         osPost += "      <ogc:ResourceId rid=\"";
    2345             :     else
    2346          14 :         osPost += "      <ogc:GmlObjectId gml:id=\"";
    2347          14 :     osPost += poFeature->GetFieldAsString(0);
    2348          14 :     osPost += "\"/>\n";
    2349          14 :     osPost += "    </ogc:Filter>\n";
    2350          14 :     osPost += "  </wfs:Update>\n";
    2351          14 :     osPost += "</wfs:Transaction>\n";
    2352             : 
    2353          14 :     CPLDebug("WFS", "Post : %s", osPost.c_str());
    2354             : 
    2355          14 :     char **papszOptions = nullptr;
    2356          14 :     papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    2357             :     papszOptions =
    2358          14 :         CSLAddNameValue(papszOptions, "HEADERS",
    2359             :                         "Content-Type: application/xml; charset=UTF-8");
    2360             : 
    2361             :     CPLHTTPResult *psResult =
    2362          14 :         poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
    2363          14 :     CSLDestroy(papszOptions);
    2364             : 
    2365          14 :     if (psResult == nullptr)
    2366             :     {
    2367           4 :         return OGRERR_FAILURE;
    2368             :     }
    2369             : 
    2370          10 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2371           8 :                "<ServiceExceptionReport") != nullptr ||
    2372           8 :         strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2373             :                "<ows:ExceptionReport") != nullptr)
    2374             :     {
    2375           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
    2376             :                  psResult->pabyData);
    2377           2 :         CPLHTTPDestroyResult(psResult);
    2378           2 :         return OGRERR_FAILURE;
    2379             :     }
    2380             : 
    2381           8 :     CPLDebug("WFS", "Response: %s", psResult->pabyData);
    2382             : 
    2383             :     CPLXMLNode *psXML =
    2384           8 :         CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
    2385           8 :     if (psXML == nullptr)
    2386             :     {
    2387           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    2388             :                  psResult->pabyData);
    2389           2 :         CPLHTTPDestroyResult(psResult);
    2390           2 :         return OGRERR_FAILURE;
    2391             :     }
    2392             : 
    2393           6 :     CPLStripXMLNamespace(psXML, nullptr, TRUE);
    2394           6 :     int bUse100Schema = false;
    2395           6 :     CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TransactionResponse");
    2396           6 :     if (psRoot == nullptr)
    2397             :     {
    2398           2 :         psRoot = CPLGetXMLNode(psXML, "=WFS_TransactionResponse");
    2399           2 :         if (psRoot)
    2400           0 :             bUse100Schema = true;
    2401             :     }
    2402           6 :     if (psRoot == nullptr)
    2403             :     {
    2404           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2405             :                  "Cannot find <TransactionResponse>");
    2406           2 :         CPLDestroyXMLNode(psXML);
    2407           2 :         CPLHTTPDestroyResult(psResult);
    2408           2 :         return OGRERR_FAILURE;
    2409             :     }
    2410             : 
    2411           4 :     if (bUse100Schema)
    2412             :     {
    2413           0 :         if (CPLGetXMLNode(psRoot, "TransactionResult.Status.FAILED"))
    2414             :         {
    2415           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Update failed : %s",
    2416             :                      psResult->pabyData);
    2417           0 :             CPLDestroyXMLNode(psXML);
    2418           0 :             CPLHTTPDestroyResult(psResult);
    2419           0 :             return OGRERR_FAILURE;
    2420             :         }
    2421             :     }
    2422             : 
    2423           4 :     CPLDestroyXMLNode(psXML);
    2424           4 :     CPLHTTPDestroyResult(psResult);
    2425             : 
    2426             :     /* Invalidate layer */
    2427           4 :     bReloadNeeded = true;
    2428           4 :     nFeatures = -1;
    2429           4 :     m_oExtents = OGREnvelope();
    2430             : 
    2431           4 :     return OGRERR_NONE;
    2432             : }
    2433             : 
    2434             : /************************************************************************/
    2435             : /*                             GetFeature()                             */
    2436             : /************************************************************************/
    2437             : 
    2438          14 : OGRFeature *OGRWFSLayer::GetFeature(GIntBig nFID)
    2439             : {
    2440          14 :     GetLayerDefn();
    2441          14 :     if (poBaseLayer == nullptr && poFeatureDefn->GetFieldIndex("gml_id") == 0)
    2442             :     {
    2443             :         /* This is lovely hackish. We assume that then gml_id will be */
    2444             :         /* layer_name.number. This is actually what we can observe with */
    2445             :         /* GeoServer and TinyOWS */
    2446             :         CPLString osVal =
    2447          14 :             CPLSPrintf("gml_id = '%s." CPL_FRMT_GIB "'", GetShortName(), nFID);
    2448          14 :         CPLString osOldSQLWhere(osSQLWhere);
    2449          14 :         SetAttributeFilter(osVal);
    2450          14 :         OGRFeature *poFeature = GetNextFeature();
    2451             :         const char *pszOldFilter =
    2452          14 :             osOldSQLWhere.size() ? osOldSQLWhere.c_str() : nullptr;
    2453          14 :         SetAttributeFilter(pszOldFilter);
    2454          14 :         if (poFeature)
    2455          12 :             return poFeature;
    2456             :     }
    2457             : 
    2458           2 :     return OGRLayer::GetFeature(nFID);
    2459             : }
    2460             : 
    2461             : /************************************************************************/
    2462             : /*                          DeleteFromFilter()                          */
    2463             : /************************************************************************/
    2464             : 
    2465          12 : OGRErr OGRWFSLayer::DeleteFromFilter(const std::string &osOGCFilter)
    2466             : {
    2467          12 :     if (!TestCapability(OLCDeleteFeature))
    2468             :     {
    2469           0 :         if (!poDS->SupportTransactions())
    2470           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2471             :                      "DeleteFromFilter() not supported: no WMS-T features "
    2472             :                      "advertized by server");
    2473           0 :         else if (!poDS->UpdateMode())
    2474           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2475             :                      "DeleteFromFilter() not supported: datasource opened as "
    2476             :                      "read-only");
    2477           0 :         return OGRERR_FAILURE;
    2478             :     }
    2479             : 
    2480          12 :     if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
    2481             :     {
    2482           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find gml_id field");
    2483           0 :         return OGRERR_FAILURE;
    2484             :     }
    2485          12 :     const char *pszShortName = GetShortName();
    2486             : 
    2487          24 :     CPLString osPost;
    2488          12 :     osPost += GetPostHeader();
    2489             : 
    2490          12 :     osPost += "  <wfs:Delete xmlns:feature=\"";
    2491          12 :     osPost += osTargetNamespace;
    2492          12 :     osPost += "\" typeName=\"feature:";
    2493          12 :     osPost += pszShortName;
    2494          12 :     osPost += "\">\n";
    2495          12 :     osPost += "    <ogc:Filter>\n";
    2496          12 :     osPost += osOGCFilter;
    2497          12 :     osPost += "    </ogc:Filter>\n";
    2498          12 :     osPost += "  </wfs:Delete>\n";
    2499          12 :     osPost += "</wfs:Transaction>\n";
    2500             : 
    2501          12 :     CPLDebug("WFS", "Post : %s", osPost.c_str());
    2502             : 
    2503          12 :     char **papszOptions = nullptr;
    2504          12 :     papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    2505             :     papszOptions =
    2506          12 :         CSLAddNameValue(papszOptions, "HEADERS",
    2507             :                         "Content-Type: application/xml; charset=UTF-8");
    2508             : 
    2509             :     CPLHTTPResult *psResult =
    2510          12 :         poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
    2511          12 :     CSLDestroy(papszOptions);
    2512             : 
    2513          12 :     if (psResult == nullptr)
    2514             :     {
    2515           4 :         return OGRERR_FAILURE;
    2516             :     }
    2517             : 
    2518           8 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2519           8 :                "<ServiceExceptionReport") != nullptr ||
    2520           8 :         strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2521             :                "<ows:ExceptionReport") != nullptr)
    2522             :     {
    2523           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
    2524             :                  psResult->pabyData);
    2525           0 :         CPLHTTPDestroyResult(psResult);
    2526           0 :         return OGRERR_FAILURE;
    2527             :     }
    2528             : 
    2529           8 :     CPLDebug("WFS", "Response: %s", psResult->pabyData);
    2530             : 
    2531             :     CPLXMLNode *psXML =
    2532           8 :         CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
    2533           8 :     if (psXML == nullptr)
    2534             :     {
    2535           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    2536             :                  psResult->pabyData);
    2537           2 :         CPLHTTPDestroyResult(psResult);
    2538           2 :         return OGRERR_FAILURE;
    2539             :     }
    2540             : 
    2541           6 :     CPLStripXMLNamespace(psXML, nullptr, TRUE);
    2542           6 :     bool bUse100Schema = false;
    2543           6 :     CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TransactionResponse");
    2544           6 :     if (psRoot == nullptr)
    2545             :     {
    2546           2 :         psRoot = CPLGetXMLNode(psXML, "=WFS_TransactionResponse");
    2547           2 :         if (psRoot)
    2548           0 :             bUse100Schema = true;
    2549             :     }
    2550           6 :     if (psRoot == nullptr)
    2551             :     {
    2552           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2553             :                  "Cannot find <TransactionResponse>");
    2554           2 :         CPLDestroyXMLNode(psXML);
    2555           2 :         CPLHTTPDestroyResult(psResult);
    2556           2 :         return OGRERR_FAILURE;
    2557             :     }
    2558             : 
    2559           4 :     if (bUse100Schema)
    2560             :     {
    2561           0 :         if (CPLGetXMLNode(psRoot, "TransactionResult.Status.FAILED"))
    2562             :         {
    2563           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Delete failed : %s",
    2564             :                      psResult->pabyData);
    2565           0 :             CPLDestroyXMLNode(psXML);
    2566           0 :             CPLHTTPDestroyResult(psResult);
    2567           0 :             return OGRERR_FAILURE;
    2568             :         }
    2569             :     }
    2570             : 
    2571           4 :     CPLDestroyXMLNode(psXML);
    2572           4 :     CPLHTTPDestroyResult(psResult);
    2573             : 
    2574             :     /* Invalidate layer */
    2575           4 :     bReloadNeeded = true;
    2576           4 :     nFeatures = -1;
    2577           4 :     m_oExtents = OGREnvelope();
    2578             : 
    2579           4 :     return OGRERR_NONE;
    2580             : }
    2581             : 
    2582             : /************************************************************************/
    2583             : /*                           DeleteFeature()                            */
    2584             : /************************************************************************/
    2585             : 
    2586          12 : OGRErr OGRWFSLayer::DeleteFeature(GIntBig nFID)
    2587             : {
    2588          12 :     if (!TestCapability(OLCDeleteFeature))
    2589             :     {
    2590           0 :         if (!poDS->SupportTransactions())
    2591           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2592             :                      "DeleteFeature() not supported: no WMS-T features "
    2593             :                      "advertized by server");
    2594           0 :         else if (!poDS->UpdateMode())
    2595           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2596             :                      "DeleteFeature() not supported: datasource opened as "
    2597             :                      "read-only");
    2598           0 :         return OGRERR_FAILURE;
    2599             :     }
    2600             : 
    2601          12 :     if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
    2602             :     {
    2603           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find gml_id field");
    2604           0 :         return OGRERR_FAILURE;
    2605             :     }
    2606             : 
    2607          12 :     OGRFeature *poFeature = GetFeature(nFID);
    2608          12 :     if (poFeature == nullptr)
    2609             :     {
    2610           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2611             :                  "Cannot find feature " CPL_FRMT_GIB, nFID);
    2612           2 :         return OGRERR_FAILURE;
    2613             :     }
    2614             : 
    2615          10 :     const char *pszGMLID = poFeature->GetFieldAsString("gml_id");
    2616          10 :     if (pszGMLID == nullptr)
    2617             :     {
    2618           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2619             :                  "Cannot delete a feature with gml_id unset");
    2620           0 :         delete poFeature;
    2621           0 :         return OGRERR_FAILURE;
    2622             :     }
    2623             : 
    2624          10 :     if (bInTransaction)
    2625             :     {
    2626           0 :         CPLError(
    2627             :             CE_Warning, CPLE_AppDefined,
    2628             :             "DeleteFeature() not yet dealt in transaction. Issued immediately");
    2629             :     }
    2630             : 
    2631          20 :     CPLString osGMLID = pszGMLID;
    2632          10 :     pszGMLID = nullptr;
    2633          10 :     delete poFeature;
    2634          10 :     poFeature = nullptr;
    2635             : 
    2636          20 :     CPLString osFilter;
    2637          10 :     osFilter = "<ogc:FeatureId fid=\"";
    2638          10 :     osFilter += osGMLID;
    2639          10 :     osFilter += "\"/>\n";
    2640          10 :     return DeleteFromFilter(osFilter);
    2641             : }
    2642             : 
    2643             : /************************************************************************/
    2644             : /*                          StartTransaction()                          */
    2645             : /************************************************************************/
    2646             : 
    2647          24 : OGRErr OGRWFSLayer::StartTransaction()
    2648             : {
    2649          24 :     if (!TestCapability(OLCTransactions))
    2650             :     {
    2651           0 :         if (!poDS->SupportTransactions())
    2652           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2653             :                      "StartTransaction() not supported: no WMS-T features "
    2654             :                      "advertized by server");
    2655           0 :         else if (!poDS->UpdateMode())
    2656           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2657             :                      "StartTransaction() not supported: datasource opened as "
    2658             :                      "read-only");
    2659           0 :         return OGRERR_FAILURE;
    2660             :     }
    2661             : 
    2662          24 :     if (bInTransaction)
    2663             :     {
    2664           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2665             :                  "StartTransaction() has already been called");
    2666           2 :         return OGRERR_FAILURE;
    2667             :     }
    2668             : 
    2669          22 :     bInTransaction = true;
    2670          22 :     osGlobalInsert = "";
    2671          22 :     nExpectedInserts = 0;
    2672          22 :     aosFIDList.resize(0);
    2673             : 
    2674          22 :     return OGRERR_NONE;
    2675             : }
    2676             : 
    2677             : /************************************************************************/
    2678             : /*                         CommitTransaction()                          */
    2679             : /************************************************************************/
    2680             : 
    2681          20 : OGRErr OGRWFSLayer::CommitTransaction()
    2682             : {
    2683          20 :     if (!TestCapability(OLCTransactions))
    2684             :     {
    2685           0 :         if (!poDS->SupportTransactions())
    2686           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2687             :                      "CommitTransaction() not supported: no WMS-T features "
    2688             :                      "advertized by server");
    2689           0 :         else if (!poDS->UpdateMode())
    2690           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2691             :                      "CommitTransaction() not supported: datasource opened as "
    2692             :                      "read-only");
    2693           0 :         return OGRERR_FAILURE;
    2694             :     }
    2695             : 
    2696          20 :     if (!bInTransaction)
    2697             :     {
    2698           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2699             :                  "StartTransaction() has not yet been called");
    2700           2 :         return OGRERR_FAILURE;
    2701             :     }
    2702             : 
    2703          18 :     if (!osGlobalInsert.empty())
    2704             :     {
    2705          16 :         CPLString osPost = GetPostHeader();
    2706          16 :         osPost += "  <wfs:Insert>\n";
    2707          16 :         osPost += osGlobalInsert;
    2708          16 :         osPost += "  </wfs:Insert>\n";
    2709          16 :         osPost += "</wfs:Transaction>\n";
    2710             : 
    2711          16 :         bInTransaction = false;
    2712          16 :         osGlobalInsert = "";
    2713          16 :         int l_nExpectedInserts = nExpectedInserts;
    2714          16 :         nExpectedInserts = 0;
    2715             : 
    2716          16 :         CPLDebug("WFS", "Post : %s", osPost.c_str());
    2717             : 
    2718          16 :         char **papszOptions = nullptr;
    2719             :         papszOptions =
    2720          16 :             CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    2721             :         papszOptions =
    2722          16 :             CSLAddNameValue(papszOptions, "HEADERS",
    2723             :                             "Content-Type: application/xml; charset=UTF-8");
    2724             : 
    2725             :         CPLHTTPResult *psResult =
    2726          16 :             poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
    2727          16 :         CSLDestroy(papszOptions);
    2728             : 
    2729          16 :         if (psResult == nullptr)
    2730             :         {
    2731           0 :             return OGRERR_FAILURE;
    2732             :         }
    2733             : 
    2734          16 :         if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2735          14 :                    "<ServiceExceptionReport") != nullptr ||
    2736          14 :             strstr(reinterpret_cast<const char *>(psResult->pabyData),
    2737             :                    "<ows:ExceptionReport") != nullptr)
    2738             :         {
    2739           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2740             :                      "Error returned by server : %s", psResult->pabyData);
    2741           2 :             CPLHTTPDestroyResult(psResult);
    2742           2 :             return OGRERR_FAILURE;
    2743             :         }
    2744             : 
    2745          14 :         CPLDebug("WFS", "Response: %s", psResult->pabyData);
    2746             : 
    2747          28 :         CPLXMLNode *psXML = CPLParseXMLString(
    2748          14 :             reinterpret_cast<const char *>(psResult->pabyData));
    2749          14 :         if (psXML == nullptr)
    2750             :         {
    2751           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    2752             :                      psResult->pabyData);
    2753           2 :             CPLHTTPDestroyResult(psResult);
    2754           2 :             return OGRERR_FAILURE;
    2755             :         }
    2756             : 
    2757          12 :         CPLStripXMLNamespace(psXML, nullptr, TRUE);
    2758          12 :         bool bUse100Schema = false;
    2759          12 :         CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TransactionResponse");
    2760          12 :         if (psRoot == nullptr)
    2761             :         {
    2762           2 :             psRoot = CPLGetXMLNode(psXML, "=WFS_TransactionResponse");
    2763           2 :             if (psRoot)
    2764           0 :                 bUse100Schema = true;
    2765             :         }
    2766             : 
    2767          12 :         if (psRoot == nullptr)
    2768             :         {
    2769           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2770             :                      "Cannot find <TransactionResponse>");
    2771           2 :             CPLDestroyXMLNode(psXML);
    2772           2 :             CPLHTTPDestroyResult(psResult);
    2773           2 :             return OGRERR_FAILURE;
    2774             :         }
    2775             : 
    2776          10 :         if (bUse100Schema)
    2777             :         {
    2778           0 :             if (CPLGetXMLNode(psRoot, "TransactionResult.Status.FAILED"))
    2779             :             {
    2780           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Insert failed : %s",
    2781             :                          psResult->pabyData);
    2782           0 :                 CPLDestroyXMLNode(psXML);
    2783           0 :                 CPLHTTPDestroyResult(psResult);
    2784           0 :                 return OGRERR_FAILURE;
    2785             :             }
    2786             : 
    2787             :             /* TODO */
    2788             :         }
    2789             :         else
    2790             :         {
    2791          10 :             int nGotInserted = atoi(
    2792             :                 CPLGetXMLValue(psRoot, "TransactionSummary.totalInserted", ""));
    2793          10 :             if (nGotInserted != l_nExpectedInserts)
    2794             :             {
    2795           2 :                 CPLError(
    2796             :                     CE_Failure, CPLE_AppDefined,
    2797             :                     "Only %d features were inserted whereas %d where expected",
    2798             :                     nGotInserted, l_nExpectedInserts);
    2799           2 :                 CPLDestroyXMLNode(psXML);
    2800           2 :                 CPLHTTPDestroyResult(psResult);
    2801           2 :                 return OGRERR_FAILURE;
    2802             :             }
    2803             : 
    2804             :             CPLXMLNode *psInsertResults =
    2805           8 :                 CPLGetXMLNode(psRoot, "InsertResults");
    2806           8 :             if (psInsertResults == nullptr)
    2807             :             {
    2808           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2809             :                          "Cannot find node InsertResults");
    2810           2 :                 CPLDestroyXMLNode(psXML);
    2811           2 :                 CPLHTTPDestroyResult(psResult);
    2812           2 :                 return OGRERR_FAILURE;
    2813             :             }
    2814             : 
    2815           6 :             aosFIDList.resize(0);
    2816             : 
    2817           6 :             CPLXMLNode *psChild = psInsertResults->psChild;
    2818           8 :             while (psChild)
    2819             :             {
    2820             :                 const char *pszFID =
    2821           4 :                     CPLGetXMLValue(psChild, "FeatureId.fid", nullptr);
    2822           4 :                 if (pszFID == nullptr)
    2823             :                 {
    2824           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Cannot find fid");
    2825           2 :                     CPLDestroyXMLNode(psXML);
    2826           2 :                     CPLHTTPDestroyResult(psResult);
    2827           2 :                     return OGRERR_FAILURE;
    2828             :                 }
    2829           2 :                 aosFIDList.push_back(pszFID);
    2830             : 
    2831           2 :                 psChild = psChild->psNext;
    2832             :             }
    2833             : 
    2834           4 :             if (static_cast<int>(aosFIDList.size()) != nGotInserted)
    2835             :             {
    2836           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2837             :                          "Inconsistent InsertResults: did not get expected FID "
    2838             :                          "count");
    2839           2 :                 CPLDestroyXMLNode(psXML);
    2840           2 :                 CPLHTTPDestroyResult(psResult);
    2841           2 :                 return OGRERR_FAILURE;
    2842             :             }
    2843             :         }
    2844             : 
    2845           2 :         CPLDestroyXMLNode(psXML);
    2846           2 :         CPLHTTPDestroyResult(psResult);
    2847             :     }
    2848             : 
    2849           4 :     bInTransaction = false;
    2850           4 :     osGlobalInsert = "";
    2851           4 :     nExpectedInserts = 0;
    2852             : 
    2853           4 :     return OGRERR_NONE;
    2854             : }
    2855             : 
    2856             : /************************************************************************/
    2857             : /*                        RollbackTransaction()                         */
    2858             : /************************************************************************/
    2859             : 
    2860           6 : OGRErr OGRWFSLayer::RollbackTransaction()
    2861             : {
    2862           6 :     if (!TestCapability(OLCTransactions))
    2863             :     {
    2864           0 :         if (!poDS->SupportTransactions())
    2865           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2866             :                      "RollbackTransaction() not supported: no WMS-T features "
    2867             :                      "advertized by server");
    2868           0 :         else if (!poDS->UpdateMode())
    2869           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2870             :                      "RollbackTransaction() not supported: datasource opened "
    2871             :                      "as read-only");
    2872           0 :         return OGRERR_FAILURE;
    2873             :     }
    2874             : 
    2875           6 :     if (!bInTransaction)
    2876             :     {
    2877           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2878             :                  "StartTransaction() has not yet been called");
    2879           2 :         return OGRERR_FAILURE;
    2880             :     }
    2881             : 
    2882           4 :     bInTransaction = false;
    2883           4 :     osGlobalInsert = "";
    2884           4 :     nExpectedInserts = 0;
    2885             : 
    2886           4 :     return OGRERR_NONE;
    2887             : }
    2888             : 
    2889             : /************************************************************************/
    2890             : /*                      SetRequiredOutputFormat()                       */
    2891             : /************************************************************************/
    2892             : 
    2893           0 : void OGRWFSLayer::SetRequiredOutputFormat(const char *pszRequiredOutputFormatIn)
    2894             : {
    2895           0 :     CPLFree(pszRequiredOutputFormat);
    2896           0 :     if (pszRequiredOutputFormatIn)
    2897             :     {
    2898           0 :         pszRequiredOutputFormat = CPLStrdup(pszRequiredOutputFormatIn);
    2899             :     }
    2900             :     else
    2901             :     {
    2902           0 :         pszRequiredOutputFormat = nullptr;
    2903             :     }
    2904           0 : }
    2905             : 
    2906             : /************************************************************************/
    2907             : /*                             SetOrderBy()                             */
    2908             : /************************************************************************/
    2909             : 
    2910           0 : void OGRWFSLayer::SetOrderBy(const std::vector<OGRWFSSortDesc> &aoSortColumnsIn)
    2911             : {
    2912           0 :     aoSortColumns = aoSortColumnsIn;
    2913           0 : }

Generated by: LCOV version 1.14