LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/wfs - ogroapifdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1096 1520 72.1 %
Date: 2024-05-04 12:52:34 Functions: 50 53 94.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OGR
       4             :  * Purpose:  Implements OGC API - Features (previously known as WFS3)
       5             :  * Author:   Even Rouault, even dot rouault at spatialys dot com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2018-2019, Even Rouault <even dot rouault at spatialys dot com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "ogrsf_frmts.h"
      30             : #include "cpl_conv.h"
      31             : #include "cpl_minixml.h"
      32             : #include "cpl_http.h"
      33             : #include "ogr_swq.h"
      34             : #include "parsexsd.h"
      35             : 
      36             : #include <algorithm>
      37             : #include <memory>
      38             : #include <vector>
      39             : #include <set>
      40             : 
      41             : // g++ -Wshadow -Wextra -std=c++11 -fPIC -g -Wall
      42             : // ogr/ogrsf_frmts/wfs/ogroapif*.cpp -shared -o ogr_OAPIF.so -Iport -Igcore
      43             : // -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gml -Iogr/ogrsf_frmts/wfs -L.
      44             : // -lgdal
      45             : 
      46             : extern "C" void RegisterOGROAPIF();
      47             : 
      48             : #define MEDIA_TYPE_OAPI_3_0 "application/vnd.oai.openapi+json;version=3.0"
      49             : #define MEDIA_TYPE_OAPI_3_0_ALT "application/openapi+json;version=3.0"
      50             : #define MEDIA_TYPE_JSON "application/json"
      51             : #define MEDIA_TYPE_GEOJSON "application/geo+json"
      52             : #define MEDIA_TYPE_TEXT_XML "text/xml"
      53             : #define MEDIA_TYPE_APPLICATION_XML "application/xml"
      54             : #define MEDIA_TYPE_JSON_SCHEMA "application/schema+json"
      55             : 
      56             : constexpr const char *OGC_CRS84_WKT =
      57             :     "GEOGCRS[\"WGS 84 (CRS84)\",ENSEMBLE[\"World Geodetic System 1984 "
      58             :     "ensemble\",MEMBER[\"World Geodetic System 1984 "
      59             :     "(Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World "
      60             :     "Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 "
      61             :     "(G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World "
      62             :     "Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 "
      63             :     "(G2139)\"],ELLIPSOID[\"WGS "
      64             :     "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]]"
      65             :     ",PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS["
      66             :     "ellipsoidal,2],AXIS[\"geodetic longitude "
      67             :     "(Lon)\",east,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS["
      68             :     "\"geodetic latitude "
      69             :     "(Lat)\",north,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE["
      70             :     "SCOPE[\"Not "
      71             :     "known.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"OGC\",\"CRS84\"]]";
      72             : 
      73             : /************************************************************************/
      74             : /*                           OGROAPIFDataset                             */
      75             : /************************************************************************/
      76             : class OGROAPIFLayer;
      77             : 
      78             : class OGROAPIFDataset final : public GDALDataset
      79             : {
      80             :     friend class OGROAPIFLayer;
      81             : 
      82             :     bool m_bMustCleanPersistent = false;
      83             :     CPLString m_osRootURL;
      84             :     CPLString m_osUserQueryParams;
      85             :     CPLString m_osUserPwd;
      86             :     int m_nPageSize = 1000;
      87             :     int m_nInitialRequestPageSize = 20;
      88             :     bool m_bPageSizeSetFromOpenOptions = false;
      89             :     std::vector<std::unique_ptr<OGRLayer>> m_apoLayers;
      90             :     std::string m_osAskedCRS{};
      91             :     OGRSpatialReference m_oAskedCRS{};
      92             :     bool m_bAskedCRSIsRequired = false;
      93             :     bool m_bServerFeaturesAxisOrderGISFriendly = false;
      94             : 
      95             :     bool m_bAPIDocLoaded = false;
      96             :     CPLJSONDocument m_oAPIDoc;
      97             : 
      98             :     bool m_bLandingPageDocLoaded = false;
      99             :     CPLJSONDocument m_oLandingPageDoc;
     100             : 
     101             :     bool m_bIgnoreSchema = false;
     102             : 
     103             :     bool Download(const CPLString &osURL, const char *pszAccept,
     104             :                   CPLString &osResult, CPLString &osContentType,
     105             :                   CPLStringList *paosHeaders = nullptr);
     106             : 
     107             :     bool DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
     108             :                       const char *pszAccept = MEDIA_TYPE_GEOJSON
     109             :                       ", " MEDIA_TYPE_JSON,
     110             :                       CPLStringList *paosHeaders = nullptr);
     111             : 
     112             :     bool LoadJSONCollection(const CPLJSONObject &oCollection,
     113             :                             const CPLJSONArray &oGlobalCRSList);
     114             :     bool LoadJSONCollections(const CPLString &osResultIn);
     115             : 
     116             :     /**
     117             :      * Determines the page size by making a call to the API endpoint to get the server's
     118             :      * default and max limits for the collection items specified by itemsUrl
     119             :      */
     120             :     void DeterminePageSizeFromAPI(const std::string &itemsUrl);
     121             : 
     122             :   public:
     123          39 :     OGROAPIFDataset() = default;
     124             :     ~OGROAPIFDataset();
     125             : 
     126          43 :     int GetLayerCount() override
     127             :     {
     128          43 :         return static_cast<int>(m_apoLayers.size());
     129             :     }
     130             : 
     131             :     OGRLayer *GetLayer(int idx) override;
     132             : 
     133             :     bool Open(GDALOpenInfo *);
     134             :     const CPLJSONDocument &GetAPIDoc();
     135             :     const CPLJSONDocument &GetLandingPageDoc();
     136             : 
     137             :     CPLString ReinjectAuthInURL(const CPLString &osURL) const;
     138             : };
     139             : 
     140             : /************************************************************************/
     141             : /*                            OGROAPIFLayer                              */
     142             : /************************************************************************/
     143             : 
     144             : class OGROAPIFLayer final : public OGRLayer
     145             : {
     146             :     OGROAPIFDataset *m_poDS = nullptr;
     147             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
     148             :     bool m_bIsGeographicCRS = false;
     149             :     bool m_bCRSHasGISFriendlyOrder = false;
     150             :     bool m_bHasEmittedContentCRSWarning = false;
     151             :     bool m_bHasEmittedJsonCRWarning = false;
     152             :     std::string m_osActiveCRS{};
     153             :     CPLString m_osURL;
     154             :     CPLString m_osPath;
     155             :     OGREnvelope m_oExtent;
     156             :     OGREnvelope m_oOriginalExtent;
     157             :     OGRSpatialReference m_oOriginalExtentCRS;
     158             :     bool m_bFeatureDefnEstablished = false;
     159             :     std::unique_ptr<GDALDataset> m_poUnderlyingDS;
     160             :     OGRLayer *m_poUnderlyingLayer = nullptr;
     161             :     GIntBig m_nFID = 1;
     162             :     CPLString m_osGetURL;
     163             :     CPLString m_osAttributeFilter;
     164             :     CPLString m_osGetID;
     165             :     std::vector<std::string> m_oSupportedCRSList{};
     166             :     OGRLayer::GetSupportedSRSListRetType m_apoSupportedCRSList{};
     167             :     bool m_bFilterMustBeClientSideEvaluated = false;
     168             :     bool m_bGotQueryableAttributes = false;
     169             :     std::set<CPLString> m_aoSetQueryableAttributes;
     170             :     bool m_bHasCQLText = false;
     171             :     // https://github.com/tschaub/ogcapi-features/blob/json-array-expression/extensions/cql/jfe/readme.md
     172             :     bool m_bHasJSONFilterExpression = false;
     173             :     GIntBig m_nTotalFeatureCount = -1;
     174             :     bool m_bHasIntIdMember = false;
     175             :     bool m_bHasStringIdMember = false;
     176             :     std::vector<std::unique_ptr<OGRFieldDefn>> m_apoFieldsFromSchema{};
     177             :     CPLString m_osDescribedByURL{};
     178             :     CPLString m_osDescribedByType{};
     179             :     bool m_bDescribedByIsXML = false;
     180             :     CPLString m_osQueryablesURL{};
     181             :     std::vector<std::string> m_aosItemAssetNames{};  // STAC specific
     182             :     CPLJSONDocument m_oCurDoc{};
     183             :     int m_iFeatureInPage = 0;
     184             : 
     185             :     void EstablishFeatureDefn();
     186             :     OGRFeature *GetNextRawFeature();
     187             :     CPLString AddFilters(const CPLString &osURL);
     188             :     CPLString BuildFilter(const swq_expr_node *poNode);
     189             :     CPLString BuildFilterCQLText(const swq_expr_node *poNode);
     190             :     CPLString BuildFilterJSONFilterExpr(const swq_expr_node *poNode);
     191             :     bool SupportsResultTypeHits();
     192             :     void GetQueryableAttributes();
     193             :     void GetSchema();
     194             :     void ComputeExtent();
     195             : 
     196             :   public:
     197             :     OGROAPIFLayer(OGROAPIFDataset *poDS, const CPLString &osName,
     198             :                   const CPLJSONArray &oBBOX, const std::string &osBBOXCrs,
     199             :                   std::vector<std::string> &&oCRSList,
     200             :                   const std::string &osActiveCRS, double dfCoordinateEpoch,
     201             :                   const CPLJSONArray &oLinks);
     202             : 
     203             :     ~OGROAPIFLayer();
     204             : 
     205             :     void SetItemAssets(const CPLJSONObject &oItemAssets);
     206             : 
     207          11 :     const char *GetName() override
     208             :     {
     209          11 :         return GetDescription();
     210             :     }
     211             : 
     212             :     OGRFeatureDefn *GetLayerDefn() override;
     213             :     void ResetReading() override;
     214             :     OGRFeature *GetNextFeature() override;
     215             :     OGRFeature *GetFeature(GIntBig) override;
     216             :     int TestCapability(const char *) override;
     217             :     GIntBig GetFeatureCount(int bForce = FALSE) override;
     218             :     OGRErr GetExtent(OGREnvelope *psExtent, int bForce = TRUE) override;
     219             : 
     220           9 :     OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent, int bForce) override
     221             :     {
     222           9 :         return OGRLayer::GetExtent(iGeomField, psExtent, bForce);
     223             :     }
     224             : 
     225             :     void SetSpatialFilter(OGRGeometry *poGeom) override;
     226             : 
     227           0 :     void SetSpatialFilter(int iGeomField, OGRGeometry *poGeom) override
     228             :     {
     229           0 :         OGRLayer::SetSpatialFilter(iGeomField, poGeom);
     230           0 :     }
     231             : 
     232             :     OGRErr SetAttributeFilter(const char *pszQuery) override;
     233             : 
     234             :     const OGRLayer::GetSupportedSRSListRetType &
     235             :     GetSupportedSRSList(int iGeomField) override;
     236             :     OGRErr SetActiveSRS(int iGeomField,
     237             :                         const OGRSpatialReference *poSRS) override;
     238             : };
     239             : 
     240             : /************************************************************************/
     241             : /*                          CheckContentType()                          */
     242             : /************************************************************************/
     243             : 
     244             : // We may ask for "application/openapi+json;version=3.0"
     245             : // and the server returns "application/openapi+json; charset=utf-8; version=3.0"
     246         331 : static bool CheckContentType(const char *pszGotContentType,
     247             :                              const char *pszExpectedContentType)
     248             : {
     249         662 :     CPLStringList aosGotTokens(CSLTokenizeString2(pszGotContentType, "; ", 0));
     250             :     CPLStringList aosExpectedTokens(
     251         662 :         CSLTokenizeString2(pszExpectedContentType, "; ", 0));
     252         566 :     for (int i = 0; i < aosExpectedTokens.size(); i++)
     253             :     {
     254         332 :         bool bFound = false;
     255         437 :         for (int j = 0; j < aosGotTokens.size(); j++)
     256             :         {
     257         340 :             if (EQUAL(aosExpectedTokens[i], aosGotTokens[j]))
     258             :             {
     259         235 :                 bFound = true;
     260         235 :                 break;
     261             :             }
     262             :         }
     263         332 :         if (!bFound)
     264          97 :             return false;
     265             :     }
     266         234 :     return true;
     267             : }
     268             : 
     269             : /************************************************************************/
     270             : /*                         ~OGROAPIFDataset()                           */
     271             : /************************************************************************/
     272             : 
     273          78 : OGROAPIFDataset::~OGROAPIFDataset()
     274             : {
     275          39 :     if (m_bMustCleanPersistent)
     276             :     {
     277          39 :         char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
     278             :                                               CPLSPrintf("OAPIF:%p", this));
     279          39 :         CPLHTTPDestroyResult(CPLHTTPFetch(m_osRootURL, papszOptions));
     280          39 :         CSLDestroy(papszOptions);
     281             :     }
     282          78 : }
     283             : 
     284             : /************************************************************************/
     285             : /*                         ReinjectAuthInURL()                          */
     286             : /************************************************************************/
     287             : 
     288             : // If source URL is https://user:pwd@server.com/bla
     289             : // and link only contains https://server.com/bla, then insert
     290             : // into it user:pwd
     291          21 : CPLString OGROAPIFDataset::ReinjectAuthInURL(const CPLString &osURL) const
     292             : {
     293          21 :     CPLString osRet(osURL);
     294             : 
     295          21 :     if (!osRet.empty() && osRet[0] == '/')
     296           0 :         osRet = m_osRootURL + osRet;
     297             : 
     298          21 :     const auto nArobaseInURLPos = m_osRootURL.find('@');
     299          42 :     if (!osRet.empty() && STARTS_WITH(m_osRootURL, "https://") &&
     300           0 :         STARTS_WITH(osRet, "https://") &&
     301          42 :         nArobaseInURLPos != std::string::npos &&
     302           0 :         osRet.find('@') == std::string::npos)
     303             :     {
     304           0 :         const auto nFirstSlashPos = m_osRootURL.find('/', strlen("https://"));
     305           0 :         if (nFirstSlashPos == std::string::npos ||
     306             :             nFirstSlashPos > nArobaseInURLPos)
     307             :         {
     308             :             auto osUserPwd = m_osRootURL.substr(
     309           0 :                 strlen("https://"), nArobaseInURLPos - strlen("https://"));
     310             :             std::string osServer(
     311             :                 nFirstSlashPos == std::string::npos
     312             :                     ? m_osRootURL.substr(nArobaseInURLPos + 1)
     313             :                     : m_osRootURL.substr(nArobaseInURLPos + 1,
     314           0 :                                          nFirstSlashPos - nArobaseInURLPos));
     315           0 :             if (STARTS_WITH(osRet, ("https://" + osServer).c_str()))
     316             :             {
     317           0 :                 osRet = "https://" + osUserPwd + "@" +
     318           0 :                         osRet.substr(strlen("https://"));
     319             :             }
     320             :         }
     321             :     }
     322          21 :     return osRet;
     323             : }
     324             : 
     325             : /************************************************************************/
     326             : /*                              Download()                              */
     327             : /************************************************************************/
     328             : 
     329         185 : bool OGROAPIFDataset::Download(const CPLString &osURL, const char *pszAccept,
     330             :                                CPLString &osResult, CPLString &osContentType,
     331             :                                CPLStringList *paosHeaders)
     332             : {
     333             : #ifndef REMOVE_HACK
     334             :     VSIStatBufL sStatBuf;
     335         185 :     if (VSIStatL(osURL, &sStatBuf) == 0)
     336             :     {
     337           0 :         CPLDebug("OAPIF", "Reading %s", osURL.c_str());
     338           0 :         GByte *pabyRet = nullptr;
     339           0 :         if (VSIIngestFile(nullptr, osURL, &pabyRet, nullptr, -1))
     340             :         {
     341           0 :             osResult = reinterpret_cast<char *>(pabyRet);
     342           0 :             CPLFree(pabyRet);
     343             :         }
     344           0 :         return false;
     345             :     }
     346             : #endif
     347         185 :     char **papszOptions = nullptr;
     348             : 
     349         185 :     if (pszAccept)
     350             :     {
     351             :         papszOptions =
     352         184 :             CSLSetNameValue(papszOptions, "HEADERS",
     353         368 :                             (CPLString("Accept: ") + pszAccept).c_str());
     354             :     }
     355             : 
     356         185 :     if (!m_osUserPwd.empty())
     357             :     {
     358             :         papszOptions =
     359           0 :             CSLSetNameValue(papszOptions, "USERPWD", m_osUserPwd.c_str());
     360             :     }
     361         185 :     m_bMustCleanPersistent = true;
     362             :     papszOptions =
     363         185 :         CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=OAPIF:%p", this));
     364         370 :     CPLString osURLWithQueryParameters(osURL);
     365           5 :     if (!m_osUserQueryParams.empty() &&
     366         375 :         osURL.find('?' + m_osUserQueryParams) == std::string::npos &&
     367         190 :         osURL.find('&' + m_osUserQueryParams) == std::string::npos)
     368             :     {
     369           5 :         if (osURL.find('?') == std::string::npos)
     370             :         {
     371           4 :             osURLWithQueryParameters += '?';
     372             :         }
     373             :         else
     374             :         {
     375           1 :             osURLWithQueryParameters += '&';
     376             :         }
     377           5 :         osURLWithQueryParameters += m_osUserQueryParams;
     378             :     }
     379             :     CPLHTTPResult *psResult =
     380         185 :         CPLHTTPFetch(osURLWithQueryParameters, papszOptions);
     381         185 :     CSLDestroy(papszOptions);
     382         185 :     if (!psResult)
     383           0 :         return false;
     384             : 
     385         185 :     if (psResult->pszErrBuf != nullptr)
     386             :     {
     387          64 :         CPLError(CE_Failure, CPLE_AppDefined, "%s",
     388          64 :                  psResult->pabyData
     389             :                      ? reinterpret_cast<const char *>(psResult->pabyData)
     390             :                      : psResult->pszErrBuf);
     391          64 :         CPLHTTPDestroyResult(psResult);
     392          64 :         return false;
     393             :     }
     394             : 
     395         121 :     if (psResult->pszContentType)
     396         120 :         osContentType = psResult->pszContentType;
     397             : 
     398             :     // Do not check content type if not specified
     399         121 :     bool bFoundExpectedContentType = pszAccept ? false : true;
     400             : 
     401         121 :     if (!bFoundExpectedContentType)
     402             :     {
     403             : #ifndef REMOVE_HACK
     404             :         // cppcheck-suppress nullPointer
     405         120 :         if (strstr(pszAccept, "json"))
     406             :         {
     407         120 :             if (strstr(osURL, "raw.githubusercontent.com") &&
     408           0 :                 strstr(osURL, ".json"))
     409             :             {
     410           0 :                 bFoundExpectedContentType = true;
     411             :             }
     412         239 :             else if (psResult->pszContentType != nullptr &&
     413         119 :                      (CheckContentType(psResult->pszContentType,
     414          47 :                                        MEDIA_TYPE_JSON) ||
     415          47 :                       CheckContentType(psResult->pszContentType,
     416             :                                        MEDIA_TYPE_GEOJSON)))
     417             :             {
     418         116 :                 bFoundExpectedContentType = true;
     419             :             }
     420             :         }
     421             : #endif
     422             : 
     423             :         // cppcheck-suppress nullPointer
     424         120 :         if (strstr(pszAccept, "xml") && psResult->pszContentType != nullptr &&
     425           0 :             (CheckContentType(psResult->pszContentType, MEDIA_TYPE_TEXT_XML) ||
     426           0 :              CheckContentType(psResult->pszContentType,
     427             :                               MEDIA_TYPE_APPLICATION_XML)))
     428             :         {
     429           0 :             bFoundExpectedContentType = true;
     430             :         }
     431             : 
     432             :         // cppcheck-suppress nullPointer
     433         241 :         if (strstr(pszAccept, MEDIA_TYPE_JSON_SCHEMA) &&
     434         121 :             psResult->pszContentType != nullptr &&
     435           1 :             (CheckContentType(psResult->pszContentType, MEDIA_TYPE_JSON) ||
     436           1 :              CheckContentType(psResult->pszContentType,
     437             :                               MEDIA_TYPE_JSON_SCHEMA)))
     438             :         {
     439           1 :             bFoundExpectedContentType = true;
     440             :         }
     441             : 
     442          58 :         for (const char *pszMediaType : {
     443             :                  MEDIA_TYPE_JSON,
     444             :                  MEDIA_TYPE_GEOJSON,
     445             :                  MEDIA_TYPE_OAPI_3_0,
     446             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     447             :                  MEDIA_TYPE_OAPI_3_0_ALT,
     448             : #endif
     449         178 :              })
     450             :         {
     451             :             // cppcheck-suppress nullPointer
     452         514 :             if (strstr(pszAccept, pszMediaType) &&
     453         338 :                 psResult->pszContentType != nullptr &&
     454         163 :                 CheckContentType(psResult->pszContentType, pszMediaType))
     455             :             {
     456         117 :                 bFoundExpectedContentType = true;
     457         117 :                 break;
     458             :             }
     459             :         }
     460             :     }
     461             : 
     462         121 :     if (!bFoundExpectedContentType)
     463             :     {
     464           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Unexpected Content-Type: %s",
     465           2 :                  psResult->pszContentType ? psResult->pszContentType
     466             :                                           : "(null)");
     467           2 :         CPLHTTPDestroyResult(psResult);
     468           2 :         return false;
     469             :     }
     470             : 
     471         119 :     if (psResult->pabyData == nullptr)
     472             :     {
     473           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     474             :                  "Empty content returned by server");
     475           0 :         CPLHTTPDestroyResult(psResult);
     476           0 :         return false;
     477             :     }
     478             : 
     479         119 :     if (paosHeaders)
     480             :     {
     481          33 :         *paosHeaders = CSLDuplicate(psResult->papszHeaders);
     482             :     }
     483             : 
     484         119 :     osResult = reinterpret_cast<const char *>(psResult->pabyData);
     485         119 :     CPLHTTPDestroyResult(psResult);
     486         119 :     return true;
     487             : }
     488             : 
     489             : /************************************************************************/
     490             : /*                           DownloadJSon()                             */
     491             : /************************************************************************/
     492             : 
     493         143 : bool OGROAPIFDataset::DownloadJSon(const CPLString &osURL,
     494             :                                    CPLJSONDocument &oDoc, const char *pszAccept,
     495             :                                    CPLStringList *paosHeaders)
     496             : {
     497         286 :     CPLString osResult;
     498         286 :     CPLString osContentType;
     499         143 :     if (!Download(osURL, pszAccept, osResult, osContentType, paosHeaders))
     500          63 :         return false;
     501          80 :     return oDoc.LoadMemory(osResult);
     502             : }
     503             : 
     504             : /************************************************************************/
     505             : /*                        GetLandingPageDoc()                           */
     506             : /************************************************************************/
     507             : 
     508          29 : const CPLJSONDocument &OGROAPIFDataset::GetLandingPageDoc()
     509             : {
     510          29 :     if (m_bLandingPageDocLoaded)
     511           0 :         return m_oLandingPageDoc;
     512          29 :     m_bLandingPageDocLoaded = true;
     513          29 :     CPL_IGNORE_RET_VAL(
     514          29 :         DownloadJSon(m_osRootURL, m_oLandingPageDoc, MEDIA_TYPE_JSON));
     515          29 :     return m_oLandingPageDoc;
     516             : }
     517             : 
     518             : /************************************************************************/
     519             : /*                            GetAPIDoc()                               */
     520             : /************************************************************************/
     521             : 
     522          33 : const CPLJSONDocument &OGROAPIFDataset::GetAPIDoc()
     523             : {
     524          33 :     if (m_bAPIDocLoaded)
     525           4 :         return m_oAPIDoc;
     526          29 :     m_bAPIDocLoaded = true;
     527             : 
     528             :     // Fetch the /api URL from the links of the landing page
     529          58 :     CPLString osAPIURL;
     530          29 :     const auto &oLandingPage = GetLandingPageDoc();
     531          29 :     if (oLandingPage.GetRoot().IsValid())
     532             :     {
     533          87 :         const auto oLinks = oLandingPage.GetRoot().GetArray("links");
     534          29 :         if (oLinks.IsValid())
     535             :         {
     536           8 :             int nCountRelAPI = 0;
     537           8 :             for (int i = 0; i < oLinks.Size(); i++)
     538             :             {
     539           8 :                 CPLJSONObject oLink = oLinks[i];
     540          16 :                 if (!oLink.IsValid() ||
     541           8 :                     oLink.GetType() != CPLJSONObject::Type::Object)
     542             :                 {
     543           0 :                     continue;
     544             :                 }
     545          16 :                 const auto osRel(oLink.GetString("rel"));
     546          16 :                 const auto osType(oLink.GetString("type"));
     547           8 :                 if (EQUAL(osRel.c_str(), "service-desc")
     548             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     549             :                     // Needed for http://beta.fmi.fi/data/3/wfs/sofp
     550           8 :                     || EQUAL(osRel.c_str(), "service")
     551             : #endif
     552             :                 )
     553             :                 {
     554           8 :                     nCountRelAPI++;
     555           8 :                     osAPIURL = ReinjectAuthInURL(oLink.GetString("href"));
     556           8 :                     if (osType == MEDIA_TYPE_OAPI_3_0
     557             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     558             :                         // Needed for http://beta.fmi.fi/data/3/wfs/sofp
     559           8 :                         || osType == MEDIA_TYPE_OAPI_3_0_ALT
     560             : #endif
     561             :                     )
     562             :                     {
     563           8 :                         nCountRelAPI = 1;
     564           8 :                         break;
     565             :                     }
     566             :                 }
     567             :             }
     568           8 :             if (!osAPIURL.empty() && nCountRelAPI > 1)
     569             :             {
     570           0 :                 osAPIURL.clear();
     571             :             }
     572             :         }
     573             :     }
     574             : 
     575          29 :     const char *pszAccept = MEDIA_TYPE_OAPI_3_0
     576             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     577             :         ", " MEDIA_TYPE_OAPI_3_0_ALT ", " MEDIA_TYPE_JSON
     578             : #endif
     579             :         ;
     580             : 
     581          29 :     if (!osAPIURL.empty())
     582             :     {
     583           8 :         CPL_IGNORE_RET_VAL(DownloadJSon(osAPIURL, m_oAPIDoc, pszAccept));
     584           8 :         return m_oAPIDoc;
     585             :     }
     586             : 
     587             : #ifndef REMOVE_HACK
     588          21 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     589          42 :     CPLString osURL(m_osRootURL + "/api");
     590          21 :     osURL = CPLGetConfigOption("OGR_WFS3_API_URL", osURL.c_str());
     591          21 :     bool bOK = DownloadJSon(osURL, m_oAPIDoc, pszAccept);
     592          21 :     CPLPopErrorHandler();
     593          21 :     CPLErrorReset();
     594          21 :     if (bOK)
     595             :     {
     596           0 :         return m_oAPIDoc;
     597             :     }
     598             : 
     599          21 :     if (DownloadJSon(m_osRootURL + "/api/", m_oAPIDoc, pszAccept))
     600             :     {
     601           0 :         return m_oAPIDoc;
     602             :     }
     603             : #endif
     604          21 :     return m_oAPIDoc;
     605             : }
     606             : 
     607             : /************************************************************************/
     608             : /*                         LoadJSONCollection()                         */
     609             : /************************************************************************/
     610             : 
     611          35 : bool OGROAPIFDataset::LoadJSONCollection(const CPLJSONObject &oCollection,
     612             :                                          const CPLJSONArray &oGlobalCRSList)
     613             : {
     614          35 :     if (oCollection.GetType() != CPLJSONObject::Type::Object)
     615           1 :         return false;
     616             : 
     617             :     // As used by https://maps.ecere.com/ogcapi/collections?f=json
     618         102 :     const auto osLayerDataType = oCollection.GetString("layerDataType");
     619          34 :     if (osLayerDataType == "Raster" || osLayerDataType == "Coverage")
     620           0 :         return false;
     621             : 
     622         102 :     CPLString osName(oCollection.GetString("id"));
     623             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     624          34 :     if (osName.empty())
     625          20 :         osName = oCollection.GetString("name");
     626          34 :     if (osName.empty())
     627           1 :         osName = oCollection.GetString("collectionId");
     628             : #endif
     629          34 :     if (osName.empty())
     630           1 :         return false;
     631             : 
     632          99 :     CPLString osTitle(oCollection.GetString("title"));
     633          99 :     CPLString osDescription(oCollection.GetString("description"));
     634          99 :     CPLJSONArray oBBOX = oCollection.GetArray("extent/spatial/bbox");
     635             : #ifndef REMOVE_HACK_FOR_NLS_FINLAND_SERVICES
     636          33 :     if (!oBBOX.IsValid())
     637          30 :         oBBOX = oCollection.GetArray("extent/spatialExtent/bbox");
     638             : #endif
     639             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
     640          33 :     if (!oBBOX.IsValid())
     641          30 :         oBBOX = oCollection.GetArray("extent/spatial");
     642             : #endif
     643          99 :     const std::string osBBOXCrs = oCollection.GetString("extent/spatial/crs");
     644             : 
     645             :     // Deal with CRS list
     646          99 :     const CPLJSONArray oCRSListOri = oCollection.GetArray("crs");
     647          66 :     std::vector<std::string> oCRSList;
     648          66 :     std::string osActiveCRS;
     649          33 :     double dfCoordinateEpoch = 0.0;
     650          33 :     if (oCRSListOri.IsValid())
     651             :     {
     652          12 :         std::set<std::string> oSetCRS;
     653          40 :         for (const auto &oCRS : oCRSListOri)
     654             :         {
     655          28 :             if (oCRS.ToString() == "#/crs")
     656             :             {
     657           4 :                 if (!oGlobalCRSList.IsValid())
     658             :                 {
     659           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     660             :                              "Collection %s refer to #/crs global CRS list, "
     661             :                              "which is missing",
     662             :                              osTitle.c_str());
     663             :                 }
     664             :                 else
     665             :                 {
     666           8 :                     for (const auto &oGlobalCRS : oGlobalCRSList)
     667             :                     {
     668          12 :                         const auto osCRS = oGlobalCRS.ToString();
     669           4 :                         if (oSetCRS.find(osCRS) == oSetCRS.end())
     670             :                         {
     671           4 :                             oSetCRS.insert(osCRS);
     672           4 :                             oCRSList.push_back(osCRS);
     673             :                         }
     674             :                     }
     675             :                 }
     676             :             }
     677             :             else
     678             :             {
     679          72 :                 const auto osCRS = oCRS.ToString();
     680          24 :                 if (oSetCRS.find(osCRS) == oSetCRS.end())
     681             :                 {
     682          24 :                     oSetCRS.insert(osCRS);
     683          24 :                     oCRSList.push_back(osCRS);
     684             :                 }
     685             :             }
     686             :         }
     687             : 
     688          12 :         if (!m_oAskedCRS.IsEmpty())
     689             :         {
     690           8 :             for (const auto &osCRS : oCRSList)
     691             :             {
     692           6 :                 OGRSpatialReference oSRS;
     693           6 :                 if (oSRS.SetFromUserInput(
     694             :                         osCRS.c_str(),
     695             :                         OGRSpatialReference::
     696           6 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     697             :                     OGRERR_NONE)
     698             :                 {
     699           6 :                     if (oSRS.IsSame(&m_oAskedCRS))
     700             :                     {
     701           2 :                         osActiveCRS = osCRS;
     702           2 :                         break;
     703             :                     }
     704             :                 }
     705             :             }
     706           4 :             if (osActiveCRS.empty())
     707             :             {
     708           2 :                 if (m_bAskedCRSIsRequired)
     709             :                 {
     710           1 :                     std::string osList;
     711           3 :                     for (const auto &osCRS : oCRSList)
     712             :                     {
     713           2 :                         if (!osList.empty())
     714           1 :                             osList += ", ";
     715           2 :                         if (osCRS.find(
     716           2 :                                 "http://www.opengis.net/def/crs/EPSG/0/") == 0)
     717             :                             osList +=
     718           2 :                                 "EPSG:" +
     719           2 :                                 osCRS.substr(strlen(
     720           1 :                                     "http://www.opengis.net/def/crs/EPSG/0/"));
     721           1 :                         else if (osCRS.find("https://www.opengis.net/def/crs/"
     722           1 :                                             "EPSG/0/") == 0)
     723             :                             osList +=
     724           0 :                                 "EPSG:" +
     725           0 :                                 osCRS.substr(strlen(
     726           0 :                                     "https://www.opengis.net/def/crs/EPSG/0/"));
     727           1 :                         else if (osCRS.find("http://www.opengis.net/def/crs/"
     728           1 :                                             "OGC/1.3/") == 0)
     729             :                             osList +=
     730           2 :                                 "OGC:" +
     731           2 :                                 osCRS.substr(strlen(
     732           1 :                                     "http://www.opengis.net/def/crs/OGC/1.3/"));
     733           0 :                         else if (osCRS.find("https://www.opengis.net/def/crs/"
     734           0 :                                             "OGC/1.3/") == 0)
     735           0 :                             osList += "OGC:" + osCRS.substr(strlen(
     736             :                                                    "https://www.opengis.net/"
     737           0 :                                                    "def/crs/OGC/1.3/"));
     738             :                         else
     739           0 :                             osList += osCRS;
     740             :                     }
     741           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     742             :                              "CRS %s not found in list of CRS valid for "
     743             :                              "collection %s. Available CRS are %s.",
     744             :                              m_osAskedCRS.c_str(), osTitle.c_str(),
     745             :                              osList.c_str());
     746           1 :                     return false;
     747             :                 }
     748             :                 else
     749             :                 {
     750           1 :                     CPLDebug("OAPIF",
     751             :                              "CRS %s not found in list of CRS valid for "
     752             :                              "collection %s",
     753             :                              m_osAskedCRS.c_str(), osTitle.c_str());
     754             :                 }
     755             :             }
     756             :         }
     757             :     }
     758             : 
     759             :     // storageCRS is in the "OGC API - Features - Part 2: Coordinate Reference
     760             :     // Systems" extension
     761          96 :     const std::string osStorageCRS = oCollection.GetString("storageCrs");
     762             :     const double dfStorageCrsCoordinateEpoch =
     763          32 :         oCollection.GetDouble("storageCrsCoordinateEpoch");
     764          32 :     if (osActiveCRS.empty() || osActiveCRS == osStorageCRS)
     765             :     {
     766          30 :         osActiveCRS = osStorageCRS;
     767          30 :         dfCoordinateEpoch = dfStorageCrsCoordinateEpoch;
     768             :     }
     769             : 
     770          96 :     const auto oLinks = oCollection.GetArray("links");
     771             :     auto poLayer = std::make_unique<OGROAPIFLayer>(
     772          32 :         this, osName, oBBOX, osBBOXCrs, std::move(oCRSList), osActiveCRS,
     773          64 :         dfCoordinateEpoch, oLinks);
     774          32 :     if (!osTitle.empty())
     775           2 :         poLayer->SetMetadataItem("TITLE", osTitle.c_str());
     776          32 :     if (!osDescription.empty())
     777           0 :         poLayer->SetMetadataItem("DESCRIPTION", osDescription.c_str());
     778          96 :     auto oTemporalInterval = oCollection.GetArray("extent/temporal/interval");
     779          32 :     if (oTemporalInterval.IsValid() && oTemporalInterval.Size() == 1 &&
     780          32 :         oTemporalInterval[0].GetType() == CPLJSONObject::Type::Array)
     781             :     {
     782           0 :         auto oArray = oTemporalInterval[0].ToArray();
     783           0 :         if (oArray.Size() == 2)
     784             :         {
     785           0 :             if (oArray[0].GetType() == CPLJSONObject::Type::String)
     786             :             {
     787           0 :                 poLayer->SetMetadataItem("TEMPORAL_INTERVAL_MIN",
     788           0 :                                          oArray[0].ToString().c_str());
     789             :             }
     790           0 :             if (oArray[1].GetType() == CPLJSONObject::Type::String)
     791             :             {
     792           0 :                 poLayer->SetMetadataItem("TEMPORAL_INTERVAL_MAX",
     793           0 :                                          oArray[1].ToString().c_str());
     794             :             }
     795             :         }
     796             :     }
     797             : 
     798             :     // STAC specific
     799          96 :     auto oItemAssets = oCollection.GetObj("item_assets");
     800          33 :     if (oItemAssets.IsValid() &&
     801           1 :         oItemAssets.GetType() == CPLJSONObject::Type::Object)
     802             :     {
     803           1 :         poLayer->SetItemAssets(oItemAssets);
     804             :     }
     805             : 
     806          32 :     auto oJSONStr = oCollection.Format(CPLJSONObject::PrettyFormat::Pretty);
     807          32 :     char *apszMetadata[2] = {&oJSONStr[0], nullptr};
     808          32 :     poLayer->SetMetadata(apszMetadata, "json:metadata");
     809             : 
     810          32 :     m_apoLayers.emplace_back(std::move(poLayer));
     811          32 :     return true;
     812             : }
     813             : 
     814             : /************************************************************************/
     815             : /*                         LoadJSONCollections()                        */
     816             : /************************************************************************/
     817             : 
     818          32 : bool OGROAPIFDataset::LoadJSONCollections(const CPLString &osResultIn)
     819             : {
     820          64 :     CPLString osResult(osResultIn);
     821          62 :     while (!osResult.empty())
     822             :     {
     823          33 :         CPLJSONDocument oDoc;
     824          33 :         if (!oDoc.LoadMemory(osResult))
     825             :         {
     826           1 :             return false;
     827             :         }
     828          32 :         const auto &oRoot = oDoc.GetRoot();
     829          64 :         CPLJSONArray oCollections = oRoot.GetArray("collections");
     830          32 :         if (!oCollections.IsValid())
     831             :         {
     832           2 :             CPLError(CE_Failure, CPLE_AppDefined, "No collections array");
     833           2 :             return false;
     834             :         }
     835             : 
     836          60 :         const auto oGlobalCRSList = oRoot.GetArray("crs");
     837             : 
     838          61 :         for (int i = 0; i < oCollections.Size(); i++)
     839             :         {
     840          31 :             LoadJSONCollection(oCollections[i], oGlobalCRSList);
     841             :         }
     842             : 
     843          30 :         osResult.clear();
     844             : 
     845             :         // Paging is a (unspecified) extension to the core used by
     846             :         // https://{api_key}:@api.planet.com/analytics
     847          60 :         const auto oLinks = oRoot.GetArray("links");
     848          30 :         if (oLinks.IsValid())
     849             :         {
     850           1 :             CPLString osNextURL;
     851           1 :             int nCountRelNext = 0;
     852           1 :             for (int i = 0; i < oLinks.Size(); i++)
     853             :             {
     854           1 :                 CPLJSONObject oLink = oLinks[i];
     855           2 :                 if (!oLink.IsValid() ||
     856           1 :                     oLink.GetType() != CPLJSONObject::Type::Object)
     857             :                 {
     858           0 :                     continue;
     859             :                 }
     860           1 :                 if (EQUAL(oLink.GetString("rel").c_str(), "next"))
     861             :                 {
     862           1 :                     osNextURL = oLink.GetString("href");
     863           1 :                     nCountRelNext++;
     864           2 :                     auto type = oLink.GetString("type");
     865           1 :                     if (type == MEDIA_TYPE_GEOJSON || type == MEDIA_TYPE_JSON)
     866             :                     {
     867           1 :                         nCountRelNext = 1;
     868           1 :                         break;
     869             :                     }
     870             :                 }
     871             :             }
     872           1 :             if (nCountRelNext == 1 && !osNextURL.empty())
     873             :             {
     874           1 :                 CPLString osContentType;
     875           1 :                 osNextURL = ReinjectAuthInURL(osNextURL);
     876           1 :                 if (!Download(osNextURL, MEDIA_TYPE_JSON, osResult,
     877             :                               osContentType))
     878             :                 {
     879           0 :                     return false;
     880             :                 }
     881             :             }
     882             :         }
     883             :     }
     884          29 :     return !m_apoLayers.empty();
     885             : }
     886             : 
     887          29 : void OGROAPIFDataset::DeterminePageSizeFromAPI(const std::string &itemsUrl)
     888             : {
     889             :     // Try to get max limit from api
     890          29 :     int nMaximum{-1};
     891          29 :     int nDefault{-1};
     892             :     // Not sure if min should be considered
     893             :     //int nMinimum { -1 };
     894          29 :     const CPLJSONDocument &oDoc{GetAPIDoc()};
     895          29 :     const auto &oRoot = oDoc.GetRoot();
     896             : 
     897          29 :     bool bFound{false};
     898             : 
     899             :     // limit from api document
     900          29 :     if (oRoot.IsValid())
     901             :     {
     902             : 
     903          58 :         const auto paths{oRoot.GetObj("paths")};
     904             : 
     905          29 :         if (paths.IsValid())
     906             :         {
     907             : 
     908           8 :             const auto pathName{itemsUrl.substr(m_osRootURL.length())};
     909           8 :             const auto path{paths.GetObj(pathName)};
     910             : 
     911           8 :             if (path.IsValid())
     912             :             {
     913             : 
     914          16 :                 const auto parameters{path.GetArray("get/parameters")};
     915             : 
     916             :                 // check $ref
     917          15 :                 for (const auto &param : parameters)
     918             :                 {
     919          14 :                     const auto ref{param.GetString("$ref")};
     920           7 :                     if (ref.find("limit") != std::string::npos)
     921             :                     {
     922             :                         // Examine ref
     923           5 :                         if (ref.find("http") == 0 &&
     924           5 :                             ref.find(".yml") == std::string::npos &&
     925           1 :                             ref.find(".yaml") ==
     926             :                                 std::string::
     927             :                                     npos)  // Remote document, skip yaml
     928             :                         {
     929             :                             // Only reinject auth if the URL matches
     930           1 :                             auto limitUrl{ref.find(m_osRootURL) == 0
     931           3 :                                               ? ReinjectAuthInURL(ref)
     932           2 :                                               : ref};
     933           1 :                             std::string fragment;
     934           1 :                             const auto hashPos{limitUrl.find('#')};
     935           1 :                             if (hashPos != std::string::npos)
     936             :                             {
     937             :                                 // Remove leading #
     938           1 :                                 fragment = limitUrl.substr(hashPos + 1);
     939           1 :                                 limitUrl = limitUrl.substr(0, hashPos);
     940             :                             }
     941           1 :                             CPLString osResult;
     942           1 :                             CPLString osContentType;
     943             :                             // Do not limit accepted content-types, external resources may have any
     944           1 :                             if (!Download(limitUrl, nullptr, osResult,
     945             :                                           osContentType))
     946             :                             {
     947           0 :                                 CPLDebug("OAPIF",
     948             :                                          "Could not download OPENAPI $ref: %s",
     949             :                                          ref.c_str());
     950           0 :                                 return;
     951             :                             }
     952             : 
     953             :                             // We cannot trust the content-type, try JSON (YAML not implemented)
     954             : 
     955             :                             // Try JSON
     956           2 :                             CPLJSONDocument oLimitDoc;
     957           1 :                             if (oLimitDoc.LoadMemory(osResult))
     958             :                             {
     959           2 :                                 const auto oLimitRoot{oLimitDoc.GetRoot()};
     960           1 :                                 if (oLimitRoot.IsValid())
     961             :                                 {
     962             :                                     const auto oLimit{
     963           2 :                                         oLimitRoot.GetObj(fragment)};
     964           1 :                                     if (oLimit.IsValid())
     965             :                                     {
     966           1 :                                         nMaximum = oLimit.GetInteger(
     967             :                                             "schema/maximum", -1);
     968             :                                         //nMinimum = oLimit.GetInteger( "schema/minimum", -1 );
     969           1 :                                         nDefault = oLimit.GetInteger(
     970             :                                             "schema/default", -1);
     971           1 :                                         bFound = true;
     972             :                                     }
     973             :                                 }
     974             :                             }
     975             :                         }
     976           3 :                         else if (ref.find('#') == 0)  // Local ref
     977             :                         {
     978           6 :                             const auto oLimit{oRoot.GetObj(ref.substr(1))};
     979           3 :                             if (oLimit.IsValid())
     980             :                             {
     981           3 :                                 nMaximum =
     982           3 :                                     oLimit.GetInteger("schema/maximum", -1);
     983             :                                 //nMinimum = oLimit.GetInteger( "schema/minimum", -1 );
     984           3 :                                 nDefault =
     985           3 :                                     oLimit.GetInteger("schema/default", -1);
     986           3 :                                 bFound = true;
     987             :                             }
     988             :                         }
     989             :                         else
     990             :                         {
     991           0 :                             CPLDebug("OAPIF", "Could not open OPENAPI $ref: %s",
     992             :                                      ref.c_str());
     993             :                         }
     994             :                     }
     995             :                 }
     996             :             }
     997             :         }
     998             :     }
     999             : 
    1000          29 :     if (bFound)
    1001             :     {
    1002             :         // Initially set to GDAL's default (1000)
    1003           4 :         int pageSize{m_nPageSize};
    1004           4 :         if (nDefault > 0 && nMaximum > 0)
    1005             :         {
    1006             :             // Use the default, but if it is below GDAL's default (1000), aim for 1000
    1007             :             // but clamp to the maximum limit
    1008           4 :             pageSize = std::min(std::max(pageSize, nDefault), nMaximum);
    1009             :         }
    1010           0 :         else if (nDefault > 0)
    1011           0 :             pageSize = std::max(pageSize, nDefault);
    1012           0 :         else if (nMaximum > 0)
    1013           0 :             pageSize = nMaximum;
    1014             : 
    1015           4 :         if (m_nPageSize != pageSize)
    1016             :         {
    1017           2 :             CPLDebug("OAPIF", "Page size set from OPENAPI schema: %d",
    1018             :                      pageSize);
    1019           2 :             m_nPageSize = pageSize;
    1020             :         }
    1021             :     }
    1022             : }
    1023             : 
    1024             : /************************************************************************/
    1025             : /*                         ConcatenateURLParts()                        */
    1026             : /************************************************************************/
    1027             : 
    1028          67 : static std::string ConcatenateURLParts(const std::string &osPart1,
    1029             :                                        const std::string &osPart2)
    1030             : {
    1031          67 :     if (!osPart1.empty() && osPart1.back() == '/' && !osPart2.empty() &&
    1032           0 :         osPart2.front() == '/')
    1033             :     {
    1034           0 :         return osPart1.substr(0, osPart1.size() - 1) + osPart2;
    1035             :     }
    1036          67 :     return osPart1 + osPart2;
    1037             : }
    1038             : 
    1039             : /************************************************************************/
    1040             : /*                              Open()                                  */
    1041             : /************************************************************************/
    1042             : 
    1043          39 : bool OGROAPIFDataset::Open(GDALOpenInfo *poOpenInfo)
    1044             : {
    1045          78 :     CPLString osCollectionDescURL;
    1046             : 
    1047          39 :     m_osRootURL = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "URL",
    1048          39 :                                        poOpenInfo->pszFilename);
    1049          39 :     if (STARTS_WITH_CI(m_osRootURL, "WFS3:"))
    1050           1 :         m_osRootURL = m_osRootURL.substr(strlen("WFS3:"));
    1051          38 :     else if (STARTS_WITH_CI(m_osRootURL, "OAPIF:"))
    1052          36 :         m_osRootURL = m_osRootURL.substr(strlen("OAPIF:"));
    1053           2 :     else if (STARTS_WITH_CI(m_osRootURL, "OAPIF_COLLECTION:"))
    1054             :     {
    1055           2 :         osCollectionDescURL = m_osRootURL.substr(strlen("OAPIF_COLLECTION:"));
    1056           2 :         m_osRootURL = osCollectionDescURL;
    1057           2 :         const char *pszStr = m_osRootURL.c_str();
    1058           2 :         const char *pszPtr = pszStr;
    1059           2 :         if (STARTS_WITH(pszPtr, "http://"))
    1060           2 :             pszPtr += strlen("http://");
    1061           0 :         else if (STARTS_WITH(pszPtr, "https://"))
    1062           0 :             pszPtr += strlen("https://");
    1063           2 :         pszPtr = strchr(pszPtr, '/');
    1064           2 :         if (pszPtr)
    1065           2 :             m_osRootURL.assign(pszStr, pszPtr - pszStr);
    1066             :     }
    1067             : 
    1068          39 :     if (osCollectionDescURL.empty())
    1069             :     {
    1070          37 :         auto nPosQuotationMark = m_osRootURL.find('?');
    1071          37 :         if (nPosQuotationMark != std::string::npos)
    1072             :         {
    1073           1 :             m_osUserQueryParams = m_osRootURL.substr(nPosQuotationMark + 1);
    1074           1 :             m_osRootURL.resize(nPosQuotationMark);
    1075             :         }
    1076             : 
    1077          37 :         auto nCollectionsPos = m_osRootURL.find("/collections/");
    1078          37 :         if (nCollectionsPos != std::string::npos)
    1079             :         {
    1080           2 :             osCollectionDescURL = m_osRootURL;
    1081           2 :             m_osRootURL.resize(nCollectionsPos);
    1082             :         }
    1083             :     }
    1084             : 
    1085          39 :     m_bIgnoreSchema = CPLTestBool(CSLFetchNameValueDef(
    1086          39 :         poOpenInfo->papszOpenOptions, "IGNORE_SCHEMA", "FALSE"));
    1087             : 
    1088          78 :     const int pageSize = atoi(
    1089          39 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "PAGE_SIZE", "-1"));
    1090             : 
    1091          39 :     if (pageSize > 0)
    1092             :     {
    1093           0 :         m_nPageSize = pageSize;
    1094           0 :         m_bPageSizeSetFromOpenOptions = true;
    1095             :     }
    1096             : 
    1097          78 :     const int initialRequestPageSize = atoi(CSLFetchNameValueDef(
    1098          39 :         poOpenInfo->papszOpenOptions, "INITIAL_REQUEST_PAGE_SIZE", "-1"));
    1099             : 
    1100          39 :     if (initialRequestPageSize >= 1)
    1101             :     {
    1102           2 :         m_nInitialRequestPageSize = initialRequestPageSize;
    1103             :     }
    1104             : 
    1105             :     m_osUserPwd =
    1106          39 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "USERPWD", "");
    1107             :     std::string osCRS =
    1108          78 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CRS", "");
    1109             :     std::string osPreferredCRS =
    1110          78 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "PREFERRED_CRS", "");
    1111          39 :     if (!osCRS.empty())
    1112             :     {
    1113           2 :         if (!osPreferredCRS.empty())
    1114             :         {
    1115           0 :             CPLError(
    1116             :                 CE_Failure, CPLE_AppDefined,
    1117             :                 "CRS and PREFERRED_CRS open options are mutually exclusive.");
    1118           0 :             return false;
    1119             :         }
    1120           2 :         m_osAskedCRS = osCRS;
    1121           2 :         if (m_oAskedCRS.SetFromUserInput(
    1122             :                 osCRS.c_str(),
    1123           2 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    1124             :             OGRERR_NONE)
    1125             :         {
    1126           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for CRS");
    1127           0 :             return false;
    1128             :         }
    1129           2 :         m_bAskedCRSIsRequired = true;
    1130             :     }
    1131          37 :     else if (!osPreferredCRS.empty())
    1132             :     {
    1133           2 :         m_osAskedCRS = osPreferredCRS;
    1134           2 :         if (m_oAskedCRS.SetFromUserInput(
    1135             :                 osPreferredCRS.c_str(),
    1136           2 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    1137             :             OGRERR_NONE)
    1138             :         {
    1139           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1140             :                      "Invalid value for PREFERRED_CRS");
    1141           0 :             return false;
    1142             :         }
    1143             :     }
    1144             : 
    1145          39 :     m_bServerFeaturesAxisOrderGISFriendly =
    1146          39 :         EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
    1147             :                                    "SERVER_FEATURE_AXIS_ORDER",
    1148             :                                    "AUTHORITY_COMPLIANT"),
    1149             :               "GIS_FRIENDLY");
    1150             : 
    1151          78 :     CPLString osResult;
    1152          78 :     CPLString osContentType;
    1153             : 
    1154          39 :     if (!osCollectionDescURL.empty())
    1155             :     {
    1156           4 :         if (!Download(osCollectionDescURL, MEDIA_TYPE_JSON, osResult,
    1157             :                       osContentType))
    1158             :         {
    1159           0 :             return false;
    1160             :         }
    1161           8 :         CPLJSONDocument oDoc;
    1162           4 :         if (!oDoc.LoadMemory(osResult))
    1163             :         {
    1164           0 :             return false;
    1165             :         }
    1166           4 :         const auto &oRoot = oDoc.GetRoot();
    1167           4 :         return LoadJSONCollection(oRoot, CPLJSONArray());
    1168             :     }
    1169             : 
    1170          35 :     if (!Download(ConcatenateURLParts(m_osRootURL, "/collections"),
    1171             :                   MEDIA_TYPE_JSON, osResult, osContentType))
    1172             :     {
    1173           3 :         return false;
    1174             :     }
    1175             : 
    1176          32 :     if (osContentType.find("json") != std::string::npos)
    1177             :     {
    1178          32 :         return LoadJSONCollections(osResult);
    1179             :     }
    1180             : 
    1181           0 :     return true;
    1182             : }
    1183             : 
    1184             : /************************************************************************/
    1185             : /*                             GetLayer()                               */
    1186             : /************************************************************************/
    1187             : 
    1188          35 : OGRLayer *OGROAPIFDataset::GetLayer(int nIndex)
    1189             : {
    1190          35 :     if (nIndex < 0 || nIndex >= GetLayerCount())
    1191           0 :         return nullptr;
    1192          35 :     return m_apoLayers[nIndex].get();
    1193             : }
    1194             : 
    1195             : /************************************************************************/
    1196             : /*                             Identify()                               */
    1197             : /************************************************************************/
    1198             : 
    1199       42280 : static int OGROAPIFDriverIdentify(GDALOpenInfo *poOpenInfo)
    1200             : 
    1201             : {
    1202       84558 :     return STARTS_WITH_CI(poOpenInfo->pszFilename, "WFS3:") ||
    1203       84486 :            STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF:") ||
    1204       84486 :            STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF_COLLECTION:");
    1205             : }
    1206             : 
    1207             : /************************************************************************/
    1208             : /*                      HasGISFriendlyAxisOrder()                       */
    1209             : /************************************************************************/
    1210             : 
    1211          20 : static bool HasGISFriendlyAxisOrder(const OGRSpatialReference *poSRS)
    1212             : {
    1213          20 :     const auto &axisMapping = poSRS->GetDataAxisToSRSAxisMapping();
    1214          38 :     return axisMapping.size() >= 2 && axisMapping[0] == 1 &&
    1215          38 :            axisMapping[1] == 2;
    1216             : }
    1217             : 
    1218             : /************************************************************************/
    1219             : /*                           OGROAPIFLayer()                             */
    1220             : /************************************************************************/
    1221             : 
    1222          32 : OGROAPIFLayer::OGROAPIFLayer(OGROAPIFDataset *poDS, const CPLString &osName,
    1223             :                              const CPLJSONArray &oBBOX,
    1224             :                              const std::string &osBBOXCrs,
    1225             :                              std::vector<std::string> &&oCRSList,
    1226             :                              const std::string &osActiveCRS,
    1227             :                              double dfCoordinateEpoch,
    1228          32 :                              const CPLJSONArray &oLinks)
    1229          32 :     : m_poDS(poDS)
    1230             : {
    1231          32 :     m_poFeatureDefn = new OGRFeatureDefn(osName);
    1232          32 :     m_poFeatureDefn->Reference();
    1233          32 :     SetDescription(osName);
    1234          32 :     m_oSupportedCRSList = std::move(oCRSList);
    1235             : 
    1236          32 :     OGRSpatialReference *poSRS = new OGRSpatialReference();
    1237          64 :     poSRS->SetFromUserInput(
    1238          32 :         !osActiveCRS.empty() ? osActiveCRS.c_str() : SRS_WKT_WGS84_LAT_LONG,
    1239             :         OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    1240          32 :     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1241          32 :     m_bIsGeographicCRS = poSRS->IsGeographic();
    1242          32 :     m_bCRSHasGISFriendlyOrder =
    1243          32 :         osActiveCRS.empty() || HasGISFriendlyAxisOrder(poSRS);
    1244          32 :     m_osActiveCRS = osActiveCRS;
    1245          32 :     if (dfCoordinateEpoch > 0)
    1246           2 :         poSRS->SetCoordinateEpoch(dfCoordinateEpoch);
    1247          32 :     m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
    1248             : 
    1249          32 :     poSRS->Release();
    1250             : 
    1251          32 :     if (oBBOX.IsValid() && oBBOX.Size() > 0)
    1252             :     {
    1253          20 :         CPLJSONArray oRealBBOX;
    1254             :         // In the final 1.0.0 spec, spatial.bbox is an array (normally with
    1255             :         // a single element) of 4-element arrays
    1256          10 :         if (oBBOX[0].GetType() == CPLJSONObject::Type::Array)
    1257             :         {
    1258           3 :             oRealBBOX = oBBOX[0].ToArray();
    1259             :         }
    1260             : #ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
    1261           7 :         else if (oBBOX.Size() == 4 || oBBOX.Size() == 6)
    1262             :         {
    1263           7 :             oRealBBOX = oBBOX;
    1264             :         }
    1265             : #endif
    1266          10 :         if (oRealBBOX.Size() == 4 || oRealBBOX.Size() == 6)
    1267             :         {
    1268          10 :             m_oOriginalExtent.MinX = oRealBBOX[0].ToDouble();
    1269          10 :             m_oOriginalExtent.MinY = oRealBBOX[1].ToDouble();
    1270          10 :             m_oOriginalExtent.MaxX =
    1271          10 :                 oRealBBOX[oRealBBOX.Size() == 6 ? 3 : 2].ToDouble();
    1272          10 :             m_oOriginalExtent.MaxY =
    1273          10 :                 oRealBBOX[oRealBBOX.Size() == 6 ? 4 : 3].ToDouble();
    1274             : 
    1275          20 :             m_oOriginalExtentCRS.SetFromUserInput(
    1276          10 :                 !osBBOXCrs.empty() ? osBBOXCrs.c_str() : OGC_CRS84_WKT,
    1277             :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    1278             : 
    1279             :             // Handle bbox over antimeridian, which we do not support properly
    1280             :             // in OGR
    1281          10 :             if (m_oOriginalExtentCRS.IsGeographic())
    1282             :             {
    1283             :                 const bool bSwitchXY =
    1284          10 :                     !HasGISFriendlyAxisOrder(&m_oOriginalExtentCRS);
    1285          10 :                 if (bSwitchXY)
    1286             :                 {
    1287           0 :                     std::swap(m_oOriginalExtent.MinX, m_oOriginalExtent.MinY);
    1288           0 :                     std::swap(m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY);
    1289             :                 }
    1290             : 
    1291          10 :                 if (m_oOriginalExtent.MinX > m_oOriginalExtent.MaxX &&
    1292           0 :                     fabs(m_oOriginalExtent.MinX) <= 180.0 &&
    1293           0 :                     fabs(m_oOriginalExtent.MaxX) <= 180.0)
    1294             :                 {
    1295           0 :                     m_oOriginalExtent.MinX = -180.0;
    1296           0 :                     m_oOriginalExtent.MaxX = 180.0;
    1297             :                 }
    1298             : 
    1299          10 :                 if (bSwitchXY)
    1300             :                 {
    1301           0 :                     std::swap(m_oOriginalExtent.MinX, m_oOriginalExtent.MinY);
    1302           0 :                     std::swap(m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY);
    1303             :                 }
    1304             :             }
    1305             :         }
    1306             :     }
    1307             : 
    1308             :     // Default to what the spec mandates for the /items URL, but check links
    1309             :     // later
    1310          32 :     m_osURL = ConcatenateURLParts(m_poDS->m_osRootURL,
    1311          64 :                                   "/collections/" + osName + "/items");
    1312          32 :     m_osPath = "/collections/" + osName + "/items";
    1313             : 
    1314          32 :     if (oLinks.IsValid())
    1315             :     {
    1316          80 :         for (int i = 0; i < oLinks.Size(); i++)
    1317             :         {
    1318          76 :             CPLJSONObject oLink = oLinks[i];
    1319         152 :             if (!oLink.IsValid() ||
    1320          76 :                 oLink.GetType() != CPLJSONObject::Type::Object)
    1321             :             {
    1322           0 :                 continue;
    1323             :             }
    1324         228 :             const auto osRel(oLink.GetString("rel"));
    1325         228 :             const auto osURL = oLink.GetString("href");
    1326         228 :             const auto type = oLink.GetString("type");
    1327          76 :             if (EQUAL(osRel.c_str(), "describedby"))
    1328             :             {
    1329          14 :                 if (type == MEDIA_TYPE_TEXT_XML ||
    1330           6 :                     type == MEDIA_TYPE_APPLICATION_XML)
    1331             :                 {
    1332           3 :                     m_osDescribedByURL = osURL;
    1333           3 :                     m_osDescribedByType = type;
    1334           3 :                     m_bDescribedByIsXML = true;
    1335             :                 }
    1336           6 :                 else if (type == MEDIA_TYPE_JSON_SCHEMA &&
    1337           1 :                          m_osDescribedByURL.empty())
    1338             :                 {
    1339           1 :                     m_osDescribedByURL = osURL;
    1340           1 :                     m_osDescribedByType = type;
    1341           1 :                     m_bDescribedByIsXML = false;
    1342             :                 }
    1343             :             }
    1344          68 :             else if (EQUAL(osRel.c_str(), "queryables"))
    1345             :             {
    1346           8 :                 if (type == MEDIA_TYPE_JSON || m_osQueryablesURL.empty())
    1347             :                 {
    1348           2 :                     m_osQueryablesURL = m_poDS->ReinjectAuthInURL(osURL);
    1349             :                 }
    1350             :             }
    1351          60 :             else if (EQUAL(osRel.c_str(), "items"))
    1352             :             {
    1353          12 :                 if (type == MEDIA_TYPE_GEOJSON)
    1354             :                 {
    1355           2 :                     m_osURL = m_poDS->ReinjectAuthInURL(osURL);
    1356             :                 }
    1357             :             }
    1358             :         }
    1359           4 :         if (!m_osDescribedByURL.empty())
    1360             :         {
    1361           4 :             m_osDescribedByURL = m_poDS->ReinjectAuthInURL(m_osDescribedByURL);
    1362             :         }
    1363             :     }
    1364             : 
    1365          32 :     OGROAPIFLayer::ResetReading();
    1366          32 : }
    1367             : 
    1368             : /************************************************************************/
    1369             : /*                          ~OGROAPIFLayer()                             */
    1370             : /************************************************************************/
    1371             : 
    1372          64 : OGROAPIFLayer::~OGROAPIFLayer()
    1373             : {
    1374          32 :     m_poFeatureDefn->Release();
    1375          64 : }
    1376             : 
    1377             : /************************************************************************/
    1378             : /*                        GetSupportedSRSList()                         */
    1379             : /************************************************************************/
    1380             : 
    1381             : const OGRLayer::GetSupportedSRSListRetType &
    1382           5 : OGROAPIFLayer::GetSupportedSRSList(int /*iGeomField*/)
    1383             : {
    1384           5 :     if (!m_oSupportedCRSList.empty() && m_apoSupportedCRSList.empty())
    1385             :     {
    1386           6 :         for (const auto &osCRS : m_oSupportedCRSList)
    1387             :         {
    1388             :             auto poSRS = std::unique_ptr<OGRSpatialReference,
    1389             :                                          OGRSpatialReferenceReleaser>(
    1390           8 :                 new OGRSpatialReference());
    1391           4 :             if (poSRS->SetFromUserInput(
    1392             :                     osCRS.c_str(),
    1393             :                     OGRSpatialReference::
    1394           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    1395             :             {
    1396           4 :                 m_apoSupportedCRSList.emplace_back(std::move(poSRS));
    1397             :             }
    1398             :         }
    1399             :     }
    1400           5 :     return m_apoSupportedCRSList;
    1401             : }
    1402             : 
    1403             : /************************************************************************/
    1404             : /*                          SetActiveSRS()                              */
    1405             : /************************************************************************/
    1406             : 
    1407           5 : OGRErr OGROAPIFLayer::SetActiveSRS(int /*iGeomField*/,
    1408             :                                    const OGRSpatialReference *poSRS)
    1409             : {
    1410           5 :     if (poSRS == nullptr)
    1411           1 :         return OGRERR_FAILURE;
    1412           4 :     const char *const apszOptions[] = {
    1413             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
    1414           7 :     for (const auto &osCRS : m_oSupportedCRSList)
    1415             :     {
    1416           6 :         OGRSpatialReference oTmpSRS;
    1417           6 :         if (oTmpSRS.SetFromUserInput(
    1418             :                 osCRS.c_str(),
    1419             :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1420          12 :                 OGRERR_NONE &&
    1421           6 :             oTmpSRS.IsSame(poSRS, apszOptions))
    1422             :         {
    1423           3 :             m_osActiveCRS = osCRS;
    1424           3 :             auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(0);
    1425           3 :             if (poGeomFieldDefn)
    1426             :             {
    1427           3 :                 OGRSpatialReference *poSRSClone = poSRS->Clone();
    1428           3 :                 poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1429           3 :                 poGeomFieldDefn->SetSpatialRef(poSRSClone);
    1430           3 :                 m_bIsGeographicCRS = poSRSClone->IsGeographic();
    1431           3 :                 m_bCRSHasGISFriendlyOrder = HasGISFriendlyAxisOrder(poSRSClone);
    1432           3 :                 poSRSClone->Release();
    1433             :             }
    1434           3 :             m_oExtent = OGREnvelope();
    1435           3 :             SetSpatialFilter(nullptr);
    1436           3 :             ResetReading();
    1437           3 :             return OGRERR_NONE;
    1438             :         }
    1439             :     }
    1440           1 :     return OGRERR_FAILURE;
    1441             : }
    1442             : 
    1443             : /************************************************************************/
    1444             : /*                         ComputeExtent()                              */
    1445             : /************************************************************************/
    1446             : 
    1447           8 : void OGROAPIFLayer::ComputeExtent()
    1448             : {
    1449           8 :     m_oExtent = m_oOriginalExtent;
    1450           8 :     const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(0);
    1451           8 :     if (poGeomFieldDefn)
    1452             :     {
    1453           8 :         const OGRSpatialReference *poSRS = poGeomFieldDefn->GetSpatialRef();
    1454           8 :         if (poSRS && !poSRS->IsSame(&m_oOriginalExtentCRS))
    1455             :         {
    1456             :             auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    1457           8 :                 OGRCreateCoordinateTransformation(&m_oOriginalExtentCRS,
    1458          16 :                                                   poSRS));
    1459           8 :             if (poCT)
    1460             :             {
    1461           8 :                 poCT->TransformBounds(
    1462             :                     m_oOriginalExtent.MinX, m_oOriginalExtent.MinY,
    1463             :                     m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY,
    1464             :                     &m_oExtent.MinX, &m_oExtent.MinY, &m_oExtent.MaxX,
    1465           8 :                     &m_oExtent.MaxY, 20);
    1466             :             }
    1467             :         }
    1468             :     }
    1469           8 : }
    1470             : 
    1471             : /************************************************************************/
    1472             : /*                         SetItemAssets()                              */
    1473             : /************************************************************************/
    1474             : 
    1475           1 : void OGROAPIFLayer::SetItemAssets(const CPLJSONObject &oItemAssets)
    1476             : {
    1477           2 :     auto oChildren = oItemAssets.GetChildren();
    1478           3 :     for (const auto &oItemAsset : oChildren)
    1479             :     {
    1480           2 :         m_aosItemAssetNames.emplace_back(oItemAsset.GetName());
    1481             :     }
    1482           1 : }
    1483             : 
    1484             : /************************************************************************/
    1485             : /*                            ResolveRefs()                             */
    1486             : /************************************************************************/
    1487             : 
    1488         205 : static CPLJSONObject ResolveRefs(const CPLJSONObject &oRoot,
    1489             :                                  const CPLJSONObject &oObj)
    1490             : {
    1491         615 :     const auto osRef = oObj.GetString("$ref");
    1492         205 :     if (osRef.empty())
    1493         179 :         return oObj;
    1494          26 :     if (STARTS_WITH(osRef.c_str(), "#/"))
    1495             :     {
    1496          25 :         return oRoot.GetObj(osRef.c_str() + 2);
    1497             :     }
    1498           2 :     CPLJSONObject oInvalid;
    1499           1 :     oInvalid.Deinit();
    1500           1 :     return oInvalid;
    1501             : }
    1502             : 
    1503             : /************************************************************************/
    1504             : /*                      BuildExampleRecursively()                       */
    1505             : /************************************************************************/
    1506             : 
    1507         205 : static bool BuildExampleRecursively(CPLJSONObject &oRes,
    1508             :                                     const CPLJSONObject &oRoot,
    1509             :                                     const CPLJSONObject &oObjIn)
    1510             : {
    1511         410 :     auto oResolvedObj = ResolveRefs(oRoot, oObjIn);
    1512         205 :     if (!oResolvedObj.IsValid())
    1513           1 :         return false;
    1514         612 :     const auto osType = oResolvedObj.GetString("type");
    1515         204 :     if (osType == "object")
    1516             :     {
    1517          87 :         const auto oAllOf = oResolvedObj.GetArray("allOf");
    1518          58 :         const auto oProperties = oResolvedObj.GetObj("properties");
    1519          29 :         if (oAllOf.IsValid())
    1520             :         {
    1521          13 :             for (int i = 0; i < oAllOf.Size(); i++)
    1522             :             {
    1523          20 :                 CPLJSONObject oChildRes;
    1524          20 :                 if (BuildExampleRecursively(oChildRes, oRoot, oAllOf[i]) &&
    1525          10 :                     oChildRes.GetType() == CPLJSONObject::Type::Object)
    1526             :                 {
    1527          20 :                     auto oChildren = oChildRes.GetChildren();
    1528          89 :                     for (const auto &oChild : oChildren)
    1529             :                     {
    1530          79 :                         oRes.Add(oChild.GetName(), oChild);
    1531             :                     }
    1532             :                 }
    1533             :             }
    1534             :         }
    1535          26 :         else if (oProperties.IsValid())
    1536             :         {
    1537          50 :             auto oChildren = oProperties.GetChildren();
    1538         206 :             for (const auto &oChild : oChildren)
    1539             :             {
    1540         362 :                 CPLJSONObject oChildRes;
    1541         181 :                 if (BuildExampleRecursively(oChildRes, oRoot, oChild))
    1542             :                 {
    1543         180 :                     oRes.Add(oChild.GetName(), oChildRes);
    1544             :                 }
    1545             :                 else
    1546             :                 {
    1547           1 :                     oRes.Add(oChild.GetName(), "unknown type");
    1548             :                 }
    1549             :             }
    1550             :         }
    1551          29 :         return true;
    1552             :     }
    1553         175 :     else if (osType == "array")
    1554             :     {
    1555          26 :         CPLJSONArray oArray;
    1556          26 :         const auto oItems = oResolvedObj.GetObj("items");
    1557          13 :         if (oItems.IsValid())
    1558             :         {
    1559          26 :             CPLJSONObject oChildRes;
    1560          13 :             if (BuildExampleRecursively(oChildRes, oRoot, oItems))
    1561             :             {
    1562          12 :                 oArray.Add(oChildRes);
    1563             :             }
    1564             :         }
    1565          13 :         oRes = std::move(oArray);
    1566          13 :         return true;
    1567             :     }
    1568         162 :     else if (osType == "string")
    1569             :     {
    1570         234 :         CPLJSONObject oTemp;
    1571         234 :         const auto osFormat = oResolvedObj.GetString("format");
    1572         117 :         if (!osFormat.empty())
    1573          27 :             oTemp.Set("_", osFormat);
    1574             :         else
    1575          90 :             oTemp.Set("_", "string");
    1576         117 :         oRes = oTemp.GetObj("_");
    1577         117 :         return true;
    1578             :     }
    1579          45 :     else if (osType == "number")
    1580             :     {
    1581          30 :         CPLJSONObject oTemp;
    1582          30 :         oTemp.Set("_", 1.25);
    1583          30 :         oRes = oTemp.GetObj("_");
    1584          30 :         return true;
    1585             :     }
    1586          15 :     else if (osType == "integer")
    1587             :     {
    1588          14 :         CPLJSONObject oTemp;
    1589          14 :         oTemp.Set("_", 1);
    1590          14 :         oRes = oTemp.GetObj("_");
    1591          14 :         return true;
    1592             :     }
    1593           1 :     else if (osType == "boolean")
    1594             :     {
    1595           0 :         CPLJSONObject oTemp;
    1596           0 :         oTemp.Set("_", true);
    1597           0 :         oRes = oTemp.GetObj("_");
    1598           0 :         return true;
    1599             :     }
    1600           1 :     else if (osType == "null")
    1601             :     {
    1602           0 :         CPLJSONObject oTemp;
    1603           0 :         oTemp.SetNull("_");
    1604           0 :         oRes = oTemp.GetObj("_");
    1605           0 :         return true;
    1606             :     }
    1607             : 
    1608           1 :     return false;
    1609             : }
    1610             : 
    1611             : /************************************************************************/
    1612             : /*                     GetObjectExampleFromSchema()                     */
    1613             : /************************************************************************/
    1614             : 
    1615           1 : static CPLJSONObject GetObjectExampleFromSchema(const std::string &osJSONSchema)
    1616             : {
    1617           2 :     CPLJSONDocument oDoc;
    1618           1 :     if (!oDoc.LoadMemory(osJSONSchema))
    1619             :     {
    1620           0 :         CPLJSONObject oInvalid;
    1621           0 :         oInvalid.Deinit();
    1622           0 :         return oInvalid;
    1623             :     }
    1624           2 :     const auto &oRoot = oDoc.GetRoot();
    1625           2 :     CPLJSONObject oRes;
    1626           1 :     BuildExampleRecursively(oRes, oRoot, oRoot);
    1627           1 :     return oRes;
    1628             : }
    1629             : 
    1630             : /************************************************************************/
    1631             : /*                            GetSchema()                               */
    1632             : /************************************************************************/
    1633             : 
    1634          29 : void OGROAPIFLayer::GetSchema()
    1635             : {
    1636          29 :     if (m_osDescribedByURL.empty() || m_poDS->m_bIgnoreSchema)
    1637          25 :         return;
    1638             : 
    1639           8 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1640             : 
    1641           4 :     if (m_bDescribedByIsXML)
    1642             :     {
    1643           6 :         std::vector<GMLFeatureClass *> apoClasses;
    1644           3 :         bool bFullyUnderstood = false;
    1645             :         bool bHaveSchema =
    1646           3 :             GMLParseXSD(m_osDescribedByURL, apoClasses, bFullyUnderstood);
    1647           3 :         if (bHaveSchema && apoClasses.size() == 1)
    1648             :         {
    1649           1 :             CPLDebug("OAPIF", "Using XML schema");
    1650           1 :             auto poGMLFeatureClass = apoClasses[0];
    1651           1 :             if (poGMLFeatureClass->GetGeometryPropertyCount() == 1)
    1652             :             {
    1653             :                 // Force linear type as we work with GeoJSON data
    1654           1 :                 m_poFeatureDefn->SetGeomType(
    1655             :                     OGR_GT_GetLinear(static_cast<OGRwkbGeometryType>(
    1656           1 :                         poGMLFeatureClass->GetGeometryProperty(0)->GetType())));
    1657             :             }
    1658             : 
    1659           1 :             const int nPropertyCount = poGMLFeatureClass->GetPropertyCount();
    1660             :             // This is a hack for
    1661             :             // http://www.pvretano.com/cubewerx/cubeserv/default/wfs/3.0.0/framework/collections/UNINCORPORATED_PL/schema
    1662             :             // The GML representation has attributes starting all with
    1663             :             // "UNINCORPORATED_PL." whereas the GeoJSON output not
    1664           2 :             CPLString osPropertyNamePrefix(GetName());
    1665           1 :             osPropertyNamePrefix += '.';
    1666           1 :             bool bAllPrefixed = true;
    1667           2 :             for (int iField = 0; iField < nPropertyCount; iField++)
    1668             :             {
    1669           1 :                 const auto poProperty = poGMLFeatureClass->GetProperty(iField);
    1670           1 :                 if (!STARTS_WITH(poProperty->GetName(),
    1671             :                                  osPropertyNamePrefix.c_str()))
    1672             :                 {
    1673           1 :                     bAllPrefixed = false;
    1674             :                 }
    1675             :             }
    1676           2 :             for (int iField = 0; iField < nPropertyCount; iField++)
    1677             :             {
    1678           1 :                 const auto poProperty = poGMLFeatureClass->GetProperty(iField);
    1679           1 :                 OGRFieldSubType eSubType = OFSTNone;
    1680             :                 const OGRFieldType eFType =
    1681           1 :                     GML_GetOGRFieldType(poProperty->GetType(), eSubType);
    1682             : 
    1683             :                 const char *pszName =
    1684           1 :                     poProperty->GetName() +
    1685           0 :                     (bAllPrefixed ? osPropertyNamePrefix.size() : 0);
    1686           2 :                 auto poField = std::make_unique<OGRFieldDefn>(pszName, eFType);
    1687           1 :                 poField->SetSubType(eSubType);
    1688           1 :                 m_apoFieldsFromSchema.emplace_back(std::move(poField));
    1689             :             }
    1690             :         }
    1691             : 
    1692           4 :         for (auto poFeatureClass : apoClasses)
    1693           1 :             delete poFeatureClass;
    1694             :     }
    1695             :     else
    1696             :     {
    1697           2 :         CPLString osContentType;
    1698           2 :         CPLString osResult;
    1699           1 :         if (!m_poDS->Download(m_osDescribedByURL, m_osDescribedByType, osResult,
    1700             :                               osContentType))
    1701             :         {
    1702           0 :             CPLDebug("OAPIF", "Could not download schema");
    1703             :         }
    1704             :         else
    1705             :         {
    1706           2 :             const auto oExample = GetObjectExampleFromSchema(osResult);
    1707             :             // CPLDebug("OAPIF", "Example from schema: %s",
    1708             :             //          oExample.Format(CPLJSONObject::PrettyFormat::Pretty).c_str());
    1709           2 :             if (oExample.IsValid() &&
    1710           1 :                 oExample.GetType() == CPLJSONObject::Type::Object)
    1711             :             {
    1712           3 :                 const auto oProperties = oExample.GetObj("properties");
    1713           2 :                 if (oProperties.IsValid() &&
    1714           1 :                     oProperties.GetType() == CPLJSONObject::Type::Object)
    1715             :                 {
    1716           1 :                     CPLDebug("OAPIF", "Using JSON schema");
    1717           2 :                     const auto oProps = oProperties.GetChildren();
    1718          19 :                     for (const auto &oProp : oProps)
    1719             :                     {
    1720          18 :                         OGRFieldType eType = OFTString;
    1721          18 :                         OGRFieldSubType eSubType = OFSTNone;
    1722          18 :                         const auto oType = oProp.GetType();
    1723          18 :                         if (oType == CPLJSONObject::Type::String)
    1724             :                         {
    1725          13 :                             if (oProp.ToString() == "date-time")
    1726             :                             {
    1727           4 :                                 eType = OFTDateTime;
    1728             :                             }
    1729           9 :                             else if (oProp.ToString() == "date")
    1730             :                             {
    1731           0 :                                 eType = OFTDate;
    1732             :                             }
    1733             :                         }
    1734           5 :                         else if (oType == CPLJSONObject::Type::Boolean)
    1735             :                         {
    1736           0 :                             eType = OFTInteger;
    1737           0 :                             eSubType = OFSTBoolean;
    1738             :                         }
    1739           5 :                         else if (oType == CPLJSONObject::Type::Double)
    1740             :                         {
    1741           0 :                             eType = OFTReal;
    1742             :                         }
    1743           5 :                         else if (oType == CPLJSONObject::Type::Integer)
    1744             :                         {
    1745           0 :                             eType = OFTInteger;
    1746             :                         }
    1747           5 :                         else if (oType == CPLJSONObject::Type::Long)
    1748             :                         {
    1749           0 :                             eType = OFTInteger64;
    1750             :                         }
    1751           5 :                         else if (oType == CPLJSONObject::Type::Array)
    1752             :                         {
    1753           4 :                             const auto oArray = oProp.ToArray();
    1754           2 :                             if (oArray.Size() > 0)
    1755             :                             {
    1756           1 :                                 if (oArray[0].GetType() ==
    1757             :                                     CPLJSONObject::Type::String)
    1758           0 :                                     eType = OFTStringList;
    1759           1 :                                 else if (oArray[0].GetType() ==
    1760             :                                          CPLJSONObject::Type::Integer)
    1761           0 :                                     eType = OFTIntegerList;
    1762             :                             }
    1763             :                         }
    1764             : 
    1765             :                         auto poField = std::make_unique<OGRFieldDefn>(
    1766          36 :                             oProp.GetName().c_str(), eType);
    1767          18 :                         poField->SetSubType(eSubType);
    1768          18 :                         m_apoFieldsFromSchema.emplace_back(std::move(poField));
    1769             :                     }
    1770             :                 }
    1771             :             }
    1772             :         }
    1773             :     }
    1774             : }
    1775             : 
    1776             : /************************************************************************/
    1777             : /*                            GetLayerDefn()                            */
    1778             : /************************************************************************/
    1779             : 
    1780         114 : OGRFeatureDefn *OGROAPIFLayer::GetLayerDefn()
    1781             : {
    1782         114 :     if (!m_bFeatureDefnEstablished)
    1783          25 :         EstablishFeatureDefn();
    1784         114 :     return m_poFeatureDefn;
    1785             : }
    1786             : 
    1787             : /************************************************************************/
    1788             : /*                        EstablishFeatureDefn()                        */
    1789             : /************************************************************************/
    1790             : 
    1791          29 : void OGROAPIFLayer::EstablishFeatureDefn()
    1792             : {
    1793          29 :     CPLAssert(!m_bFeatureDefnEstablished);
    1794          29 :     m_bFeatureDefnEstablished = true;
    1795             : 
    1796          29 :     GetSchema();
    1797             : 
    1798          29 :     if (!m_poDS->m_bPageSizeSetFromOpenOptions)
    1799             :     {
    1800          29 :         const int nOldPageSize{m_poDS->m_nPageSize};
    1801          29 :         m_poDS->DeterminePageSizeFromAPI(m_osURL);
    1802             :         // cppcheck-suppress knownConditionTrueFalse
    1803          29 :         if (nOldPageSize != m_poDS->m_nPageSize)
    1804             :         {
    1805           4 :             m_osGetURL = CPLURLAddKVP(m_osGetURL, "limit",
    1806           4 :                                       CPLSPrintf("%d", m_poDS->m_nPageSize));
    1807             :         }
    1808             :     }
    1809             : 
    1810          29 :     CPLJSONDocument oDoc;
    1811          29 :     CPLString osURL(m_osURL);
    1812             : 
    1813          58 :     osURL = CPLURLAddKVP(
    1814             :         osURL, "limit",
    1815          29 :         CPLSPrintf("%d", std::min(m_poDS->m_nInitialRequestPageSize,
    1816          58 :                                   m_poDS->m_nPageSize)));
    1817          29 :     if (!m_poDS->DownloadJSon(osURL, oDoc))
    1818           0 :         return;
    1819             : 
    1820          29 :     CPLString osTmpFilename(CPLSPrintf("/vsimem/oapif_%p.json", this));
    1821          29 :     oDoc.Save(osTmpFilename);
    1822             :     std::unique_ptr<GDALDataset> poDS(GDALDataset::FromHandle(
    1823             :         GDALOpenEx(osTmpFilename, GDAL_OF_VECTOR | GDAL_OF_INTERNAL, nullptr,
    1824          29 :                    nullptr, nullptr)));
    1825          29 :     VSIUnlink(osTmpFilename);
    1826          29 :     if (!poDS.get())
    1827           1 :         return;
    1828          28 :     OGRLayer *poLayer = poDS->GetLayer(0);
    1829          28 :     if (!poLayer)
    1830           0 :         return;
    1831          28 :     OGRFeatureDefn *poFeatureDefn = poLayer->GetLayerDefn();
    1832          28 :     if (m_poFeatureDefn->GetGeomType() == wkbUnknown)
    1833             :     {
    1834          27 :         m_poFeatureDefn->SetGeomType(poFeatureDefn->GetGeomType());
    1835             :     }
    1836          28 :     if (m_apoFieldsFromSchema.empty())
    1837             :     {
    1838          83 :         for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
    1839             :         {
    1840          57 :             m_poFeatureDefn->AddFieldDefn(poFeatureDefn->GetFieldDefn(i));
    1841             :         }
    1842             :     }
    1843             :     else
    1844             :     {
    1845           3 :         if (poFeatureDefn->GetFieldCount() > 0 &&
    1846           1 :             strcmp(poFeatureDefn->GetFieldDefn(0)->GetNameRef(), "id") == 0)
    1847             :         {
    1848           0 :             m_poFeatureDefn->AddFieldDefn(poFeatureDefn->GetFieldDefn(0));
    1849             :         }
    1850          21 :         for (const auto &poField : m_apoFieldsFromSchema)
    1851             :         {
    1852          19 :             m_poFeatureDefn->AddFieldDefn(poField.get());
    1853             :         }
    1854             :         // In case there would be properties found in sample, but not in
    1855             :         // schema...
    1856           3 :         for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
    1857             :         {
    1858           1 :             auto poFDefn = poFeatureDefn->GetFieldDefn(i);
    1859           1 :             if (m_poFeatureDefn->GetFieldIndex(poFDefn->GetNameRef()) < 0)
    1860             :             {
    1861           1 :                 m_poFeatureDefn->AddFieldDefn(poFDefn);
    1862             :             }
    1863             :         }
    1864             :     }
    1865             : 
    1866          30 :     for (const auto &osItemAsset : m_aosItemAssetNames)
    1867             :     {
    1868           4 :         OGRFieldDefn oFieldDefn(("asset_" + osItemAsset + "_href").c_str(),
    1869           4 :                                 OFTString);
    1870             :         // cppcheck-suppress danglingTemporaryLifetime
    1871           2 :         m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
    1872             :     }
    1873             : 
    1874          56 :     const auto &oRoot = oDoc.GetRoot();
    1875          28 :     GIntBig nFeatures = oRoot.GetLong("numberMatched", -1);
    1876          28 :     if (nFeatures >= 0)
    1877             :     {
    1878           9 :         m_nTotalFeatureCount = nFeatures;
    1879             :     }
    1880             : 
    1881          84 :     auto oFeatures = oRoot.GetArray("features");
    1882          28 :     if (oFeatures.IsValid() && oFeatures.Size() > 0)
    1883             :     {
    1884          24 :         auto eType = oFeatures[0].GetObj("id").GetType();
    1885          24 :         if (eType == CPLJSONObject::Type::Integer ||
    1886             :             eType == CPLJSONObject::Type::Long)
    1887             :         {
    1888           3 :             m_bHasIntIdMember = true;
    1889             :         }
    1890          21 :         else if (eType == CPLJSONObject::Type::String)
    1891             :         {
    1892           7 :             m_bHasStringIdMember = true;
    1893             :         }
    1894             :     }
    1895             : }
    1896             : 
    1897             : /************************************************************************/
    1898             : /*                           ResetReading()                             */
    1899             : /************************************************************************/
    1900             : 
    1901          76 : void OGROAPIFLayer::ResetReading()
    1902             : {
    1903          76 :     m_poUnderlyingDS.reset();
    1904          76 :     m_poUnderlyingLayer = nullptr;
    1905          76 :     m_nFID = 1;
    1906          76 :     m_osGetURL = m_osURL;
    1907          76 :     if (!m_osGetID.empty())
    1908             :     {
    1909           4 :         m_osGetURL += "/" + m_osGetID;
    1910             :     }
    1911             :     else
    1912             :     {
    1913          72 :         if (m_poDS->m_nPageSize > 0)
    1914             :         {
    1915         144 :             m_osGetURL = CPLURLAddKVP(m_osGetURL, "limit",
    1916         144 :                                       CPLSPrintf("%d", m_poDS->m_nPageSize));
    1917             :         }
    1918          72 :         m_osGetURL = AddFilters(m_osGetURL);
    1919             :     }
    1920          76 :     m_oCurDoc = CPLJSONDocument();
    1921          76 :     m_iFeatureInPage = 0;
    1922          76 : }
    1923             : 
    1924             : /************************************************************************/
    1925             : /*                           AddFilters()                               */
    1926             : /************************************************************************/
    1927             : 
    1928          74 : CPLString OGROAPIFLayer::AddFilters(const CPLString &osURL)
    1929             : {
    1930          74 :     CPLString osURLNew(osURL);
    1931          74 :     if (m_poFilterGeom)
    1932             :     {
    1933           8 :         double dfMinX = m_sFilterEnvelope.MinX;
    1934           8 :         double dfMinY = m_sFilterEnvelope.MinY;
    1935           8 :         double dfMaxX = m_sFilterEnvelope.MaxX;
    1936           8 :         double dfMaxY = m_sFilterEnvelope.MaxY;
    1937           8 :         bool bAddBBoxFilter = true;
    1938           8 :         if (m_bIsGeographicCRS)
    1939             :         {
    1940           6 :             dfMinX = std::max(dfMinX, -180.0);
    1941           6 :             dfMinY = std::max(dfMinY, -90.0);
    1942           6 :             dfMaxX = std::min(dfMaxX, 180.0);
    1943           6 :             dfMaxY = std::min(dfMaxY, 90.0);
    1944           8 :             bAddBBoxFilter = dfMinX > -180.0 || dfMinY > -90.0 ||
    1945           8 :                              dfMaxX < 180.0 || dfMaxY < 90.0;
    1946             :         }
    1947           8 :         if (bAddBBoxFilter)
    1948             :         {
    1949           7 :             if (!m_bCRSHasGISFriendlyOrder)
    1950             :             {
    1951           2 :                 std::swap(dfMinX, dfMinY);
    1952           2 :                 std::swap(dfMaxX, dfMaxY);
    1953             :             }
    1954          14 :             osURLNew = CPLURLAddKVP(osURLNew, "bbox",
    1955             :                                     CPLSPrintf("%.18g,%.18g,%.18g,%.18g",
    1956           7 :                                                dfMinX, dfMinY, dfMaxX, dfMaxY));
    1957           7 :             if (!m_osActiveCRS.empty())
    1958             :             {
    1959             :                 osURLNew =
    1960           4 :                     CPLURLAddKVP(osURLNew, "bbox-crs", m_osActiveCRS.c_str());
    1961             :             }
    1962             :         }
    1963             :     }
    1964          74 :     if (!m_osActiveCRS.empty())
    1965             :     {
    1966          17 :         osURLNew = CPLURLAddKVP(osURLNew, "crs", m_osActiveCRS.c_str());
    1967             :     }
    1968          74 :     if (!m_osAttributeFilter.empty())
    1969             :     {
    1970          15 :         if (osURLNew.find('?') == std::string::npos)
    1971           0 :             osURLNew += "?";
    1972             :         else
    1973          15 :             osURLNew += "&";
    1974          15 :         osURLNew += m_osAttributeFilter;
    1975             :     }
    1976          74 :     return osURLNew;
    1977             : }
    1978             : 
    1979             : /************************************************************************/
    1980             : /*                         GetNextRawFeature()                          */
    1981             : /************************************************************************/
    1982             : 
    1983          38 : OGRFeature *OGROAPIFLayer::GetNextRawFeature()
    1984             : {
    1985          38 :     if (!m_bFeatureDefnEstablished)
    1986           3 :         EstablishFeatureDefn();
    1987             : 
    1988          38 :     OGRFeature *poSrcFeature = nullptr;
    1989             :     while (true)
    1990             :     {
    1991          41 :         if (m_poUnderlyingLayer == nullptr)
    1992             :         {
    1993          35 :             if (m_osGetURL.empty())
    1994           2 :                 return nullptr;
    1995             : 
    1996          33 :             m_oCurDoc = CPLJSONDocument();
    1997             : 
    1998          33 :             CPLString osURL(m_osGetURL);
    1999          33 :             m_osGetURL.clear();
    2000          33 :             CPLStringList aosHeaders;
    2001          33 :             if (!m_poDS->DownloadJSon(osURL, m_oCurDoc,
    2002             :                                       MEDIA_TYPE_GEOJSON ", " MEDIA_TYPE_JSON,
    2003             :                                       &aosHeaders))
    2004             :             {
    2005           0 :                 return nullptr;
    2006             :             }
    2007             : 
    2008             :             const std::string osContentCRS =
    2009          33 :                 aosHeaders.FetchNameValueDef("Content-Crs", "");
    2010          33 :             if (!m_bHasEmittedContentCRSWarning && !osContentCRS.empty())
    2011             :             {
    2012           7 :                 if (m_osActiveCRS.empty())
    2013             :                 {
    2014           0 :                     if (osContentCRS !=
    2015           0 :                             "<http://www.opengis.net/def/crs/OGC/1.3/CRS84>" &&
    2016           0 :                         osContentCRS !=
    2017             :                             "<http://www.opengis.net/def/crs/OGC/0/CRS84h>")
    2018             :                     {
    2019           0 :                         m_bHasEmittedContentCRSWarning = true;
    2020           0 :                         CPLDebug("OAPIF",
    2021             :                                  "Got Content-CRS = %s, but expected OGC:CRS84 "
    2022             :                                  "instead. "
    2023             :                                  "Content-CRS will be ignored",
    2024             :                                  osContentCRS.c_str());
    2025             :                     }
    2026             :                 }
    2027             :                 else
    2028             :                 {
    2029           7 :                     if (osContentCRS != '<' + m_osActiveCRS + '>')
    2030             :                     {
    2031           0 :                         m_bHasEmittedContentCRSWarning = true;
    2032           0 :                         CPLDebug(
    2033             :                             "OAPIF",
    2034             :                             "Got Content-CRS = %s, but expected %s instead. "
    2035             :                             "Content-CRS will be ignored",
    2036             :                             osContentCRS.c_str(), m_osActiveCRS.c_str());
    2037             :                     }
    2038             :                 }
    2039             :             }
    2040          26 :             else if (!m_bHasEmittedContentCRSWarning)
    2041             :             {
    2042          26 :                 if (!m_osActiveCRS.empty())
    2043             :                 {
    2044           1 :                     m_bHasEmittedContentCRSWarning = true;
    2045           1 :                     CPLDebug("OAPIF",
    2046             :                              "Dit not get Content-CRS header. "
    2047             :                              "Assuming %s is returned",
    2048             :                              m_osActiveCRS.c_str());
    2049             :                 }
    2050             :             }
    2051             : 
    2052          33 :             if (!m_bHasEmittedJsonCRWarning)
    2053             :             {
    2054          99 :                 const auto oJsonCRS = m_oCurDoc.GetRoot().GetObj("crs");
    2055          33 :                 if (oJsonCRS.IsValid())
    2056             :                 {
    2057           0 :                     m_bHasEmittedJsonCRWarning = true;
    2058           0 :                     CPLDebug("OAPIF",
    2059             :                              "JSON response contains %s. It will be ignored.",
    2060           0 :                              oJsonCRS.ToString().c_str());
    2061             :                 }
    2062             :             }
    2063             : 
    2064          33 :             CPLString osTmpFilename(CPLSPrintf("/vsimem/oapif_%p.json", this));
    2065          33 :             m_oCurDoc.Save(osTmpFilename);
    2066             :             m_poUnderlyingDS =
    2067          66 :                 std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
    2068             :                     GDALOpenEx(osTmpFilename, GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
    2069          33 :                                nullptr, nullptr, nullptr)));
    2070          33 :             VSIUnlink(osTmpFilename);
    2071          33 :             if (!m_poUnderlyingDS.get())
    2072             :             {
    2073           0 :                 return nullptr;
    2074             :             }
    2075          33 :             m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
    2076          33 :             if (!m_poUnderlyingLayer)
    2077             :             {
    2078           0 :                 m_poUnderlyingDS.reset();
    2079           0 :                 return nullptr;
    2080             :             }
    2081             : 
    2082             :             // To avoid issues with implementations having a non-relevant
    2083             :             // next link, make sure the current page is not empty
    2084             :             // We could even check that the feature count is the page size
    2085             :             // actually
    2086          33 :             if (m_poUnderlyingLayer->GetFeatureCount() > 0 && m_osGetID.empty())
    2087             :             {
    2088          93 :                 CPLJSONArray oLinks = m_oCurDoc.GetRoot().GetArray("links");
    2089          31 :                 if (oLinks.IsValid())
    2090             :                 {
    2091           3 :                     int nCountRelNext = 0;
    2092           6 :                     std::string osNextURL;
    2093           7 :                     for (int i = 0; i < oLinks.Size(); i++)
    2094             :                     {
    2095           7 :                         CPLJSONObject oLink = oLinks[i];
    2096          14 :                         if (!oLink.IsValid() ||
    2097           7 :                             oLink.GetType() != CPLJSONObject::Type::Object)
    2098             :                         {
    2099           0 :                             continue;
    2100             :                         }
    2101           7 :                         if (EQUAL(oLink.GetString("rel").c_str(), "next"))
    2102             :                         {
    2103           3 :                             nCountRelNext++;
    2104           6 :                             auto type = oLink.GetString("type");
    2105           3 :                             if (type == MEDIA_TYPE_GEOJSON ||
    2106           0 :                                 type == MEDIA_TYPE_JSON)
    2107             :                             {
    2108           3 :                                 m_osGetURL = oLink.GetString("href");
    2109           3 :                                 break;
    2110             :                             }
    2111           0 :                             else if (type.empty())
    2112             :                             {
    2113           0 :                                 osNextURL = oLink.GetString("href");
    2114             :                             }
    2115             :                         }
    2116             :                     }
    2117           3 :                     if (nCountRelNext == 1 && m_osGetURL.empty())
    2118             :                     {
    2119             :                         // In case we go a "rel": "next" without a "type"
    2120           0 :                         m_osGetURL = std::move(osNextURL);
    2121             :                     }
    2122             :                 }
    2123             : 
    2124             : #ifdef no_longer_used
    2125             :                 // Recommendation /rec/core/link-header
    2126             :                 if (m_osGetURL.empty())
    2127             :                 {
    2128             :                     for (int i = 0; i < aosHeaders.size(); i++)
    2129             :                     {
    2130             :                         CPLDebug("OAPIF", "%s", aosHeaders[i]);
    2131             :                         if (STARTS_WITH_CI(aosHeaders[i], "Link=") &&
    2132             :                             strstr(aosHeaders[i], "rel=\"next\"") &&
    2133             :                             strstr(aosHeaders[i],
    2134             :                                    "type=\"" MEDIA_TYPE_GEOJSON "\""))
    2135             :                         {
    2136             :                             const char *pszStart = strchr(aosHeaders[i], '<');
    2137             :                             if (pszStart)
    2138             :                             {
    2139             :                                 const char *pszEnd = strchr(pszStart + 1, '>');
    2140             :                                 if (pszEnd)
    2141             :                                 {
    2142             :                                     m_osGetURL = pszStart + 1;
    2143             :                                     m_osGetURL.resize(pszEnd - pszStart - 1);
    2144             :                                 }
    2145             :                             }
    2146             :                             break;
    2147             :                         }
    2148             :                     }
    2149             :                 }
    2150             : #endif
    2151             : 
    2152          31 :                 if (!m_osGetURL.empty())
    2153             :                 {
    2154           3 :                     m_osGetURL = m_poDS->ReinjectAuthInURL(m_osGetURL);
    2155             :                 }
    2156             :             }
    2157             :         }
    2158             : 
    2159          39 :         poSrcFeature = m_poUnderlyingLayer->GetNextFeature();
    2160          39 :         if (poSrcFeature)
    2161             :         {
    2162          36 :             break;
    2163             :         }
    2164           3 :         m_poUnderlyingDS.reset();
    2165           3 :         m_poUnderlyingLayer = nullptr;
    2166           3 :         m_iFeatureInPage = 0;
    2167           3 :     }
    2168             : 
    2169          36 :     OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
    2170          36 :     poFeature->SetFrom(poSrcFeature);
    2171             : 
    2172             :     // Collect STAC assets href
    2173           2 :     if (!m_aosItemAssetNames.empty() && m_poUnderlyingLayer != nullptr &&
    2174          38 :         m_oCurDoc.GetRoot().GetArray("features").Size() ==
    2175          40 :             m_poUnderlyingLayer->GetFeatureCount() &&
    2176          38 :         m_iFeatureInPage < m_oCurDoc.GetRoot().GetArray("features").Size())
    2177             :     {
    2178             :         auto m_oFeature =
    2179           6 :             m_oCurDoc.GetRoot().GetArray("features")[m_iFeatureInPage];
    2180           6 :         auto oAssets = m_oFeature["assets"];
    2181           6 :         for (const auto &osAssetName : m_aosItemAssetNames)
    2182             :         {
    2183          12 :             auto href = oAssets[osAssetName]["href"];
    2184           4 :             if (href.IsValid() && href.GetType() == CPLJSONObject::Type::String)
    2185             :             {
    2186           2 :                 poFeature->SetField(("asset_" + osAssetName + "_href").c_str(),
    2187           4 :                                     href.ToString().c_str());
    2188             :             }
    2189             :         }
    2190             :     }
    2191          36 :     m_iFeatureInPage++;
    2192             : 
    2193          36 :     auto poGeom = poFeature->GetGeometryRef();
    2194          36 :     if (poGeom)
    2195             :     {
    2196          20 :         if (!m_bCRSHasGISFriendlyOrder &&
    2197           3 :             !m_poDS->m_bServerFeaturesAxisOrderGISFriendly)
    2198           2 :             poGeom->swapXY();
    2199          20 :         poGeom->assignSpatialReference(GetSpatialRef());
    2200             :     }
    2201          36 :     if (m_bHasIntIdMember)
    2202             :     {
    2203           4 :         poFeature->SetFID(poSrcFeature->GetFID());
    2204             :     }
    2205             :     else
    2206             :     {
    2207          32 :         poFeature->SetFID(m_nFID);
    2208          32 :         m_nFID++;
    2209             :     }
    2210          36 :     delete poSrcFeature;
    2211          36 :     return poFeature;
    2212             : }
    2213             : 
    2214             : /************************************************************************/
    2215             : /*                            GetFeature()                              */
    2216             : /************************************************************************/
    2217             : 
    2218           1 : OGRFeature *OGROAPIFLayer::GetFeature(GIntBig nFID)
    2219             : {
    2220           1 :     if (!m_bFeatureDefnEstablished)
    2221           0 :         EstablishFeatureDefn();
    2222           1 :     if (!m_bHasIntIdMember)
    2223           0 :         return OGRLayer::GetFeature(nFID);
    2224             : 
    2225           1 :     m_osGetID.Printf(CPL_FRMT_GIB, nFID);
    2226           1 :     ResetReading();
    2227           1 :     auto poRet = GetNextRawFeature();
    2228           1 :     m_osGetID.clear();
    2229           1 :     ResetReading();
    2230           1 :     return poRet;
    2231             : }
    2232             : 
    2233             : /************************************************************************/
    2234             : /*                         GetNextFeature()                             */
    2235             : /************************************************************************/
    2236             : 
    2237          37 : OGRFeature *OGROAPIFLayer::GetNextFeature()
    2238             : {
    2239             :     while (true)
    2240             :     {
    2241          37 :         OGRFeature *poFeature = GetNextRawFeature();
    2242          37 :         if (poFeature == nullptr)
    2243           2 :             return nullptr;
    2244             : 
    2245          76 :         if ((m_poFilterGeom == nullptr ||
    2246          70 :              FilterGeometry(poFeature->GetGeometryRef())) &&
    2247          35 :             (m_poAttrQuery == nullptr || !m_bFilterMustBeClientSideEvaluated ||
    2248           4 :              m_poAttrQuery->Evaluate(poFeature)))
    2249             :         {
    2250          34 :             return poFeature;
    2251             :         }
    2252             :         else
    2253             :         {
    2254           1 :             delete poFeature;
    2255             :         }
    2256           1 :     }
    2257             : }
    2258             : 
    2259             : /************************************************************************/
    2260             : /*                      SupportsResultTypeHits()                        */
    2261             : /************************************************************************/
    2262             : 
    2263           3 : bool OGROAPIFLayer::SupportsResultTypeHits()
    2264             : {
    2265           6 :     CPLJSONDocument oDoc = m_poDS->GetAPIDoc();
    2266           3 :     if (oDoc.GetRoot().GetString("openapi").empty())
    2267           1 :         return false;
    2268             : 
    2269             :     CPLJSONArray oParameters =
    2270           4 :         oDoc.GetRoot().GetObj("paths").GetObj(m_osPath).GetObj("get").GetArray(
    2271           6 :             "parameters");
    2272           2 :     if (!oParameters.IsValid())
    2273           0 :         return false;
    2274           2 :     for (int i = 0; i < oParameters.Size(); i++)
    2275             :     {
    2276           2 :         CPLJSONObject oParam = oParameters[i];
    2277           4 :         CPLString osRef = oParam.GetString("$ref");
    2278           2 :         if (!osRef.empty() && osRef.find("#/") == 0)
    2279             :         {
    2280           2 :             oParam = oDoc.GetRoot().GetObj(osRef.substr(2));
    2281             : #ifndef REMOVE_HACK
    2282             :             // Needed for
    2283             :             // http://www.pvretano.com/cubewerx/cubeserv/default/wfs/3.0.0/foundation/api
    2284             :             // that doesn't define #/components/parameters/resultType
    2285           2 :             if (osRef == "#/components/parameters/resultType")
    2286           2 :                 return true;
    2287             : #endif
    2288             :         }
    2289           0 :         if (oParam.GetString("name") == "resultType" &&
    2290           0 :             oParam.GetString("in") == "query")
    2291             :         {
    2292           0 :             CPLJSONArray oEnum = oParam.GetArray("schema/enum");
    2293           0 :             for (int j = 0; j < oEnum.Size(); j++)
    2294             :             {
    2295           0 :                 if (oEnum[j].ToString() == "hits")
    2296           0 :                     return true;
    2297             :             }
    2298           0 :             return false;
    2299             :         }
    2300             :     }
    2301             : 
    2302           0 :     return false;
    2303             : }
    2304             : 
    2305             : /************************************************************************/
    2306             : /*                         GetFeatureCount()                            */
    2307             : /************************************************************************/
    2308             : 
    2309           9 : GIntBig OGROAPIFLayer::GetFeatureCount(int bForce)
    2310             : {
    2311             : 
    2312           9 :     if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
    2313             :     {
    2314           9 :         GetLayerDefn();
    2315           9 :         if (m_nTotalFeatureCount >= 0)
    2316             :         {
    2317           6 :             return m_nTotalFeatureCount;
    2318             :         }
    2319             :     }
    2320             : 
    2321           3 :     if (SupportsResultTypeHits() && !m_bFilterMustBeClientSideEvaluated)
    2322             :     {
    2323           2 :         CPLString osURL(m_osURL);
    2324           2 :         osURL = CPLURLAddKVP(osURL, "resultType", "hits");
    2325           2 :         osURL = AddFilters(osURL);
    2326             : #ifndef REMOVE_HACK
    2327           2 :         bool bGMLRequest = m_osURL.find("cubeserv") != std::string::npos;
    2328             : #else
    2329             :         constexpr bool bGMLRequest = false;
    2330             : #endif
    2331           2 :         if (bGMLRequest)
    2332             :         {
    2333           0 :             CPLString osResult;
    2334           0 :             CPLString osContentType;
    2335           0 :             if (m_poDS->Download(osURL, MEDIA_TYPE_TEXT_XML, osResult,
    2336             :                                  osContentType))
    2337             :             {
    2338           0 :                 CPLXMLNode *psDoc = CPLParseXMLString(osResult);
    2339           0 :                 if (psDoc)
    2340             :                 {
    2341           0 :                     CPLXMLTreeCloser oCloser(psDoc);
    2342           0 :                     CPL_IGNORE_RET_VAL(oCloser);
    2343           0 :                     CPLStripXMLNamespace(psDoc, nullptr, true);
    2344             :                     CPLString osNumberMatched = CPLGetXMLValue(
    2345           0 :                         psDoc, "=FeatureCollection.numberMatched", "");
    2346           0 :                     if (!osNumberMatched.empty())
    2347           0 :                         return CPLAtoGIntBig(osNumberMatched);
    2348             :                 }
    2349             :             }
    2350             :         }
    2351             :         else
    2352             :         {
    2353           2 :             CPLJSONDocument oDoc;
    2354           2 :             if (m_poDS->DownloadJSon(osURL, oDoc))
    2355             :             {
    2356           2 :                 GIntBig nFeatures = oDoc.GetRoot().GetLong("numberMatched", -1);
    2357           2 :                 if (nFeatures >= 0)
    2358           2 :                     return nFeatures;
    2359             :             }
    2360             :         }
    2361             :     }
    2362             : 
    2363           1 :     return OGRLayer::GetFeatureCount(bForce);
    2364             : }
    2365             : 
    2366             : /************************************************************************/
    2367             : /*                             GetExtent()                              */
    2368             : /************************************************************************/
    2369             : 
    2370          10 : OGRErr OGROAPIFLayer::GetExtent(OGREnvelope *psEnvelope, int bForce)
    2371             : {
    2372          10 :     if (m_oOriginalExtent.IsInit())
    2373             :     {
    2374          10 :         if (!m_oExtent.IsInit())
    2375           8 :             ComputeExtent();
    2376          10 :         *psEnvelope = m_oExtent;
    2377          10 :         return OGRERR_NONE;
    2378             :     }
    2379           0 :     return OGRLayer::GetExtent(psEnvelope, bForce);
    2380             : }
    2381             : 
    2382             : /************************************************************************/
    2383             : /*                          SetSpatialFilter()                          */
    2384             : /************************************************************************/
    2385             : 
    2386          10 : void OGROAPIFLayer::SetSpatialFilter(OGRGeometry *poGeomIn)
    2387             : {
    2388          10 :     InstallFilter(poGeomIn);
    2389             : 
    2390          10 :     ResetReading();
    2391          10 : }
    2392             : 
    2393             : /************************************************************************/
    2394             : /*                      OGRWF3ParseDateTime()                           */
    2395             : /************************************************************************/
    2396             : 
    2397           5 : static int OGRWF3ParseDateTime(const char *pszValue, int &nYear, int &nMonth,
    2398             :                                int &nDay, int &nHour, int &nMinute,
    2399             :                                int &nSecond)
    2400             : {
    2401           5 :     int ret = sscanf(pszValue, "%04d/%02d/%02d %02d:%02d:%02d", &nYear, &nMonth,
    2402             :                      &nDay, &nHour, &nMinute, &nSecond);
    2403           5 :     if (ret >= 3)
    2404           0 :         return ret;
    2405           5 :     return sscanf(pszValue, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth,
    2406           5 :                   &nDay, &nHour, &nMinute, &nSecond);
    2407             : }
    2408             : 
    2409             : /************************************************************************/
    2410             : /*                       SerializeDateTime()                            */
    2411             : /************************************************************************/
    2412             : 
    2413           5 : static CPLString SerializeDateTime(int nDateComponents, int nYear, int nMonth,
    2414             :                                    int nDay, int nHour, int nMinute,
    2415             :                                    int nSecond)
    2416             : {
    2417           5 :     CPLString osRet;
    2418           5 :     osRet.Printf("%04d-%02d-%02dT", nYear, nMonth, nDay);
    2419           5 :     if (nDateComponents >= 4)
    2420             :     {
    2421           3 :         osRet += CPLSPrintf("%02d", nHour);
    2422           3 :         if (nDateComponents >= 5)
    2423           3 :             osRet += CPLSPrintf(":%02d", nMinute);
    2424           3 :         if (nDateComponents >= 6)
    2425           3 :             osRet += CPLSPrintf(":%02d", nSecond);
    2426           3 :         osRet += "Z";
    2427             :     }
    2428           5 :     return osRet;
    2429             : }
    2430             : 
    2431             : /************************************************************************/
    2432             : /*                            BuildFilter()                             */
    2433             : /************************************************************************/
    2434             : 
    2435          11 : CPLString OGROAPIFLayer::BuildFilter(const swq_expr_node *poNode)
    2436             : {
    2437          11 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
    2438           3 :         poNode->nSubExprCount == 2)
    2439             :     {
    2440           3 :         const auto leftExpr = poNode->papoSubExpr[0];
    2441           3 :         const auto rightExpr = poNode->papoSubExpr[1];
    2442             : 
    2443             :         // Detect expression: datetime >=|> XXX and datetime <=|< XXXX
    2444           3 :         if (leftExpr->eNodeType == SNT_OPERATION &&
    2445           3 :             (leftExpr->nOperation == SWQ_GT ||
    2446           3 :              leftExpr->nOperation == SWQ_GE) &&
    2447           1 :             leftExpr->nSubExprCount == 2 &&
    2448           1 :             leftExpr->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2449           1 :             leftExpr->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
    2450           1 :             rightExpr->eNodeType == SNT_OPERATION &&
    2451           1 :             (rightExpr->nOperation == SWQ_LT ||
    2452           1 :              rightExpr->nOperation == SWQ_LE) &&
    2453           1 :             rightExpr->nSubExprCount == 2 &&
    2454           1 :             rightExpr->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2455           1 :             rightExpr->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
    2456           1 :             leftExpr->papoSubExpr[0]->field_index ==
    2457           1 :                 rightExpr->papoSubExpr[0]->field_index &&
    2458           1 :             leftExpr->papoSubExpr[1]->field_type == SWQ_TIMESTAMP &&
    2459           1 :             rightExpr->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
    2460             :         {
    2461           1 :             const OGRFieldDefn *poFieldDefn = GetLayerDefn()->GetFieldDefn(
    2462           1 :                 leftExpr->papoSubExpr[0]->field_index);
    2463           2 :             if (poFieldDefn && (poFieldDefn->GetType() == OFTDate ||
    2464           1 :                                 poFieldDefn->GetType() == OFTDateTime))
    2465             :             {
    2466           1 :                 CPLString osExpr;
    2467             :                 {
    2468           1 :                     int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2469           1 :                         nSecond = 0;
    2470           1 :                     int nDateComponents = OGRWF3ParseDateTime(
    2471           1 :                         leftExpr->papoSubExpr[1]->string_value, nYear, nMonth,
    2472             :                         nDay, nHour, nMinute, nSecond);
    2473           1 :                     if (nDateComponents >= 3)
    2474             :                     {
    2475             :                         osExpr =
    2476           1 :                             "datetime=" +
    2477           2 :                             SerializeDateTime(nDateComponents, nYear, nMonth,
    2478           1 :                                               nDay, nHour, nMinute, nSecond);
    2479             :                     }
    2480             :                 }
    2481           1 :                 if (!osExpr.empty())
    2482             :                 {
    2483           1 :                     int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2484           1 :                         nSecond = 0;
    2485           1 :                     int nDateComponents = OGRWF3ParseDateTime(
    2486           1 :                         rightExpr->papoSubExpr[1]->string_value, nYear, nMonth,
    2487             :                         nDay, nHour, nMinute, nSecond);
    2488           1 :                     if (nDateComponents >= 3)
    2489             :                     {
    2490             :                         osExpr +=
    2491             :                             "%2F"  // '/' URL encoded
    2492           2 :                             + SerializeDateTime(nDateComponents, nYear, nMonth,
    2493           1 :                                                 nDay, nHour, nMinute, nSecond);
    2494           1 :                         return osExpr;
    2495             :                     }
    2496             :                 }
    2497             :             }
    2498             :         }
    2499             : 
    2500             :         // For AND, we can deal with a failure in one of the branch
    2501             :         // since client-side will do that extra filtering
    2502           4 :         CPLString osFilter1 = BuildFilter(leftExpr);
    2503           4 :         CPLString osFilter2 = BuildFilter(rightExpr);
    2504           2 :         if (!osFilter1.empty() && !osFilter2.empty())
    2505             :         {
    2506           2 :             return osFilter1 + "&" + osFilter2;
    2507             :         }
    2508           1 :         else if (!osFilter1.empty())
    2509           1 :             return osFilter1;
    2510             :         else
    2511           0 :             return osFilter2;
    2512             :     }
    2513           8 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2514           8 :              poNode->nOperation == SWQ_EQ && poNode->nSubExprCount == 2 &&
    2515           5 :              poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2516           5 :              poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT)
    2517             :     {
    2518           5 :         const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
    2519             :         const OGRFieldDefn *poFieldDefn =
    2520           5 :             GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2521             :         int nDateComponents;
    2522           5 :         int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2523           5 :             nSecond = 0;
    2524          15 :         if (m_bHasStringIdMember &&
    2525           6 :             strcmp(poFieldDefn->GetNameRef(), "id") == 0 &&
    2526           1 :             poNode->papoSubExpr[1]->field_type == SWQ_STRING)
    2527             :         {
    2528           1 :             m_osGetID = poNode->papoSubExpr[1]->string_value;
    2529             :         }
    2530           8 :         else if (poFieldDefn &&
    2531           8 :                  m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
    2532           8 :                      m_aoSetQueryableAttributes.end())
    2533             :         {
    2534             :             char *pszEscapedFieldName =
    2535           2 :                 CPLEscapeString(poFieldDefn->GetNameRef(), -1, CPLES_URL);
    2536           2 :             const CPLString osEscapedFieldName(pszEscapedFieldName);
    2537           2 :             CPLFree(pszEscapedFieldName);
    2538             : 
    2539           2 :             if (poNode->papoSubExpr[1]->field_type == SWQ_STRING)
    2540             :             {
    2541           2 :                 char *pszEscapedValue = CPLEscapeString(
    2542           1 :                     poNode->papoSubExpr[1]->string_value, -1, CPLES_URL);
    2543           2 :                 CPLString osRet(osEscapedFieldName);
    2544           1 :                 osRet += "=";
    2545           1 :                 osRet += pszEscapedValue;
    2546           1 :                 CPLFree(pszEscapedValue);
    2547           1 :                 return osRet;
    2548             :             }
    2549           1 :             if (poNode->papoSubExpr[1]->field_type == SWQ_INTEGER)
    2550             :             {
    2551           2 :                 CPLString osRet(osEscapedFieldName);
    2552           1 :                 osRet += "=";
    2553             :                 osRet +=
    2554           1 :                     CPLSPrintf(CPL_FRMT_GIB, poNode->papoSubExpr[1]->int_value);
    2555           1 :                 return osRet;
    2556             :             }
    2557             :         }
    2558           2 :         else if (poFieldDefn &&
    2559           4 :                  (poFieldDefn->GetType() == OFTDate ||
    2560           2 :                   poFieldDefn->GetType() == OFTDateTime) &&
    2561           5 :                  poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP &&
    2562           1 :                  (nDateComponents = OGRWF3ParseDateTime(
    2563           1 :                       poNode->papoSubExpr[1]->string_value, nYear, nMonth, nDay,
    2564             :                       nHour, nMinute, nSecond)) >= 3)
    2565             :         {
    2566           2 :             return "datetime=" + SerializeDateTime(nDateComponents, nYear,
    2567             :                                                    nMonth, nDay, nHour, nMinute,
    2568           1 :                                                    nSecond);
    2569           2 :         }
    2570             :     }
    2571           3 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2572           3 :              (poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
    2573           2 :               poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE) &&
    2574           2 :              poNode->nSubExprCount == 2 &&
    2575           2 :              poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2576           2 :              poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
    2577           2 :              poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
    2578             :     {
    2579           2 :         const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
    2580             :         const OGRFieldDefn *poFieldDefn =
    2581           2 :             GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2582             :         int nDateComponents;
    2583           2 :         int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2584           2 :             nSecond = 0;
    2585           2 :         if (poFieldDefn &&
    2586           4 :             (poFieldDefn->GetType() == OFTDate ||
    2587           6 :              poFieldDefn->GetType() == OFTDateTime) &&
    2588           2 :             (nDateComponents = OGRWF3ParseDateTime(
    2589           2 :                  poNode->papoSubExpr[1]->string_value, nYear, nMonth, nDay,
    2590             :                  nHour, nMinute, nSecond)) >= 3)
    2591             :         {
    2592             :             CPLString osDT(SerializeDateTime(nDateComponents, nYear, nMonth,
    2593           4 :                                              nDay, nHour, nMinute, nSecond));
    2594           2 :             if (poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE)
    2595             :             {
    2596           2 :                 return "datetime=" + osDT + "%2F..";
    2597             :             }
    2598             :             else
    2599             :             {
    2600           2 :                 return "datetime=..%2F" + osDT;
    2601             :             }
    2602             :         }
    2603             :     }
    2604           3 :     m_bFilterMustBeClientSideEvaluated = true;
    2605           3 :     return CPLString();
    2606             : }
    2607             : 
    2608             : /************************************************************************/
    2609             : /*                       BuildFilterCQLText()                           */
    2610             : /************************************************************************/
    2611             : 
    2612           0 : CPLString OGROAPIFLayer::BuildFilterCQLText(const swq_expr_node *poNode)
    2613             : {
    2614           0 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
    2615           0 :         poNode->nSubExprCount == 2)
    2616             :     {
    2617           0 :         const auto leftExpr = poNode->papoSubExpr[0];
    2618           0 :         const auto rightExpr = poNode->papoSubExpr[1];
    2619             : 
    2620             :         // For AND, we can deal with a failure in one of the branch
    2621             :         // since client-side will do that extra filtering
    2622           0 :         CPLString osFilter1 = BuildFilterCQLText(leftExpr);
    2623           0 :         CPLString osFilter2 = BuildFilterCQLText(rightExpr);
    2624           0 :         if (!osFilter1.empty() && !osFilter2.empty())
    2625             :         {
    2626           0 :             return '(' + osFilter1 + ") AND (" + osFilter2 + ')';
    2627             :         }
    2628           0 :         else if (!osFilter1.empty())
    2629           0 :             return osFilter1;
    2630             :         else
    2631           0 :             return osFilter2;
    2632             :     }
    2633           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2634           0 :              poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2)
    2635             :     {
    2636           0 :         const auto leftExpr = poNode->papoSubExpr[0];
    2637           0 :         const auto rightExpr = poNode->papoSubExpr[1];
    2638             : 
    2639           0 :         CPLString osFilter1 = BuildFilterCQLText(leftExpr);
    2640           0 :         CPLString osFilter2 = BuildFilterCQLText(rightExpr);
    2641           0 :         if (!osFilter1.empty() && !osFilter2.empty())
    2642             :         {
    2643           0 :             return '(' + osFilter1 + ") OR (" + osFilter2 + ')';
    2644           0 :         }
    2645             :     }
    2646           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2647           0 :              poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1)
    2648             :     {
    2649           0 :         const auto childExpr = poNode->papoSubExpr[0];
    2650           0 :         CPLString osFilterChild = BuildFilterCQLText(childExpr);
    2651           0 :         if (!osFilterChild.empty())
    2652             :         {
    2653           0 :             return "NOT (" + osFilterChild + ')';
    2654           0 :         }
    2655             :     }
    2656           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2657           0 :              poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1 &&
    2658           0 :              poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN)
    2659             :     {
    2660           0 :         const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
    2661             :         const OGRFieldDefn *poFieldDefn =
    2662           0 :             GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2663           0 :         if (poFieldDefn)
    2664             :         {
    2665           0 :             return CPLString("(") + poFieldDefn->GetNameRef() + " IS NULL)";
    2666           0 :         }
    2667             :     }
    2668           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2669           0 :              (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_NE ||
    2670           0 :               poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
    2671           0 :               poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE ||
    2672           0 :               poNode->nOperation == SWQ_LIKE ||
    2673           0 :               poNode->nOperation == SWQ_ILIKE) &&
    2674           0 :              poNode->nSubExprCount == 2 &&
    2675           0 :              poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2676           0 :              poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT)
    2677             :     {
    2678           0 :         const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
    2679             :         const OGRFieldDefn *poFieldDefn =
    2680           0 :             GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2681           0 :         if (m_bHasStringIdMember && poNode->nOperation == SWQ_EQ &&
    2682           0 :             strcmp(poFieldDefn->GetNameRef(), "id") == 0 &&
    2683           0 :             poNode->papoSubExpr[1]->field_type == SWQ_STRING)
    2684             :         {
    2685           0 :             m_osGetID = poNode->papoSubExpr[1]->string_value;
    2686             :         }
    2687           0 :         else if (poFieldDefn &&
    2688           0 :                  m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
    2689           0 :                      m_aoSetQueryableAttributes.end())
    2690             :         {
    2691           0 :             CPLString osRet(poFieldDefn->GetNameRef());
    2692           0 :             switch (poNode->nOperation)
    2693             :             {
    2694           0 :                 case SWQ_EQ:
    2695           0 :                     osRet += " = ";
    2696           0 :                     break;
    2697           0 :                 case SWQ_NE:
    2698           0 :                     osRet += " <> ";
    2699           0 :                     break;
    2700           0 :                 case SWQ_GT:
    2701           0 :                     osRet += " > ";
    2702           0 :                     break;
    2703           0 :                 case SWQ_GE:
    2704           0 :                     osRet += " >= ";
    2705           0 :                     break;
    2706           0 :                 case SWQ_LT:
    2707           0 :                     osRet += " < ";
    2708           0 :                     break;
    2709           0 :                 case SWQ_LE:
    2710           0 :                     osRet += " <= ";
    2711           0 :                     break;
    2712           0 :                 case SWQ_LIKE:
    2713           0 :                     osRet += " LIKE ";
    2714           0 :                     break;
    2715           0 :                 case SWQ_ILIKE:
    2716           0 :                     osRet += " ILIKE ";
    2717           0 :                     break;
    2718           0 :                 default:
    2719           0 :                     CPLAssert(false);
    2720             :                     break;
    2721             :             }
    2722           0 :             if (poNode->papoSubExpr[1]->field_type == SWQ_STRING)
    2723             :             {
    2724           0 :                 osRet += '\'';
    2725           0 :                 osRet += CPLString(poNode->papoSubExpr[1]->string_value)
    2726           0 :                              .replaceAll('\'', "''");
    2727           0 :                 osRet += '\'';
    2728           0 :                 return osRet;
    2729             :             }
    2730           0 :             if (poNode->papoSubExpr[1]->field_type == SWQ_INTEGER ||
    2731           0 :                 poNode->papoSubExpr[1]->field_type == SWQ_INTEGER64)
    2732             :             {
    2733             :                 osRet +=
    2734           0 :                     CPLSPrintf(CPL_FRMT_GIB, poNode->papoSubExpr[1]->int_value);
    2735           0 :                 return osRet;
    2736             :             }
    2737           0 :             if (poNode->papoSubExpr[1]->field_type == SWQ_FLOAT)
    2738             :             {
    2739             :                 osRet +=
    2740           0 :                     CPLSPrintf("%.16g", poNode->papoSubExpr[1]->float_value);
    2741           0 :                 return osRet;
    2742             :             }
    2743           0 :             if (poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
    2744             :             {
    2745             :                 int nDateComponents;
    2746           0 :                 int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2747           0 :                     nSecond = 0;
    2748           0 :                 if ((poFieldDefn->GetType() == OFTDate ||
    2749           0 :                      poFieldDefn->GetType() == OFTDateTime) &&
    2750           0 :                     (nDateComponents = OGRWF3ParseDateTime(
    2751           0 :                          poNode->papoSubExpr[1]->string_value, nYear, nMonth,
    2752             :                          nDay, nHour, nMinute, nSecond)) >= 3)
    2753             :                 {
    2754             :                     CPLString osDT(SerializeDateTime(nDateComponents, nYear,
    2755             :                                                      nMonth, nDay, nHour,
    2756           0 :                                                      nMinute, nSecond));
    2757           0 :                     osRet += '\'';
    2758           0 :                     osRet += osDT;
    2759           0 :                     osRet += '\'';
    2760           0 :                     return osRet;
    2761             :                 }
    2762             :             }
    2763             :         }
    2764             :     }
    2765             : 
    2766           0 :     m_bFilterMustBeClientSideEvaluated = true;
    2767           0 :     return CPLString();
    2768             : }
    2769             : 
    2770             : /************************************************************************/
    2771             : /*                     BuildFilterJSONFilterExpr()                      */
    2772             : /************************************************************************/
    2773             : 
    2774           0 : CPLString OGROAPIFLayer::BuildFilterJSONFilterExpr(const swq_expr_node *poNode)
    2775             : {
    2776           0 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
    2777           0 :         poNode->nSubExprCount == 2)
    2778             :     {
    2779           0 :         const auto leftExpr = poNode->papoSubExpr[0];
    2780           0 :         const auto rightExpr = poNode->papoSubExpr[1];
    2781             : 
    2782             :         // For AND, we can deal with a failure in one of the branch
    2783             :         // since client-side will do that extra filtering
    2784           0 :         CPLString osFilter1 = BuildFilterJSONFilterExpr(leftExpr);
    2785           0 :         CPLString osFilter2 = BuildFilterJSONFilterExpr(rightExpr);
    2786           0 :         if (!osFilter1.empty() && !osFilter2.empty())
    2787             :         {
    2788           0 :             return "[\"all\"," + osFilter1 + ',' + osFilter2 + ']';
    2789             :         }
    2790           0 :         else if (!osFilter1.empty())
    2791           0 :             return osFilter1;
    2792             :         else
    2793           0 :             return osFilter2;
    2794             :     }
    2795           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2796           0 :              poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2)
    2797             :     {
    2798           0 :         const auto leftExpr = poNode->papoSubExpr[0];
    2799           0 :         const auto rightExpr = poNode->papoSubExpr[1];
    2800             : 
    2801           0 :         CPLString osFilter1 = BuildFilterJSONFilterExpr(leftExpr);
    2802           0 :         CPLString osFilter2 = BuildFilterJSONFilterExpr(rightExpr);
    2803           0 :         if (!osFilter1.empty() && !osFilter2.empty())
    2804             :         {
    2805           0 :             return "[\"any\"," + osFilter1 + ',' + osFilter2 + ']';
    2806           0 :         }
    2807             :     }
    2808           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2809           0 :              poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1)
    2810             :     {
    2811           0 :         const auto childExpr = poNode->papoSubExpr[0];
    2812           0 :         CPLString osFilterChild = BuildFilterJSONFilterExpr(childExpr);
    2813           0 :         if (!osFilterChild.empty())
    2814             :         {
    2815           0 :             return "[\"!\"," + osFilterChild + ']';
    2816           0 :         }
    2817             :     }
    2818           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2819           0 :              poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1)
    2820             :     {
    2821           0 :         const auto childExpr = poNode->papoSubExpr[0];
    2822           0 :         CPLString osFilterChild = BuildFilterJSONFilterExpr(childExpr);
    2823           0 :         if (!osFilterChild.empty())
    2824             :         {
    2825           0 :             return "[\"==\"," + osFilterChild + ",null]";
    2826           0 :         }
    2827             :     }
    2828           0 :     else if (poNode->eNodeType == SNT_OPERATION &&
    2829           0 :              (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_NE ||
    2830           0 :               poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
    2831           0 :               poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE ||
    2832           0 :               poNode->nOperation == SWQ_LIKE) &&
    2833           0 :              poNode->nSubExprCount == 2)
    2834             :     {
    2835           0 :         if (m_bHasStringIdMember && poNode->nOperation == SWQ_EQ &&
    2836           0 :             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
    2837           0 :             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
    2838           0 :             poNode->papoSubExpr[1]->field_type == SWQ_STRING)
    2839             :         {
    2840           0 :             const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
    2841             :             const OGRFieldDefn *poFieldDefn =
    2842           0 :                 GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2843           0 :             if (strcmp(poFieldDefn->GetNameRef(), "id") == 0)
    2844             :             {
    2845           0 :                 m_osGetID = poNode->papoSubExpr[1]->string_value;
    2846           0 :                 return CPLString();
    2847             :             }
    2848             :         }
    2849             : 
    2850           0 :         CPLString osRet("[\"");
    2851           0 :         switch (poNode->nOperation)
    2852             :         {
    2853           0 :             case SWQ_EQ:
    2854           0 :                 osRet += "==";
    2855           0 :                 break;
    2856           0 :             case SWQ_NE:
    2857           0 :                 osRet += "!=";
    2858           0 :                 break;
    2859           0 :             case SWQ_GT:
    2860           0 :                 osRet += ">";
    2861           0 :                 break;
    2862           0 :             case SWQ_GE:
    2863           0 :                 osRet += ">=";
    2864           0 :                 break;
    2865           0 :             case SWQ_LT:
    2866           0 :                 osRet += "<";
    2867           0 :                 break;
    2868           0 :             case SWQ_LE:
    2869           0 :                 osRet += "<=";
    2870           0 :                 break;
    2871           0 :             case SWQ_LIKE:
    2872           0 :                 osRet += "like";
    2873           0 :                 break;
    2874           0 :             default:
    2875           0 :                 CPLAssert(false);
    2876             :                 break;
    2877             :         }
    2878           0 :         osRet += "\",";
    2879           0 :         CPLString osFilter1 = BuildFilterJSONFilterExpr(poNode->papoSubExpr[0]);
    2880           0 :         CPLString osFilter2 = BuildFilterJSONFilterExpr(poNode->papoSubExpr[1]);
    2881           0 :         if (!osFilter1.empty() && !osFilter2.empty())
    2882             :         {
    2883           0 :             osRet += osFilter1;
    2884           0 :             osRet += ',';
    2885           0 :             osRet += osFilter2;
    2886           0 :             osRet += ']';
    2887           0 :             return osRet;
    2888           0 :         }
    2889             :     }
    2890           0 :     else if (poNode->eNodeType == SNT_COLUMN)
    2891             :     {
    2892           0 :         const int nFieldIdx = poNode->field_index;
    2893             :         const OGRFieldDefn *poFieldDefn =
    2894           0 :             GetLayerDefn()->GetFieldDefn(nFieldIdx);
    2895           0 :         if (poFieldDefn &&
    2896           0 :             m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
    2897           0 :                 m_aoSetQueryableAttributes.end())
    2898             :         {
    2899           0 :             CPLString osRet("[\"get\",\"");
    2900           0 :             osRet += CPLString(poFieldDefn->GetNameRef())
    2901           0 :                          .replaceAll('\\', "\\\\")
    2902           0 :                          .replaceAll('"', "\\\"");
    2903           0 :             osRet += "\"]";
    2904           0 :             return osRet;
    2905             :         }
    2906             :     }
    2907           0 :     else if (poNode->eNodeType == SNT_CONSTANT)
    2908             :     {
    2909           0 :         if (poNode->field_type == SWQ_STRING)
    2910             :         {
    2911           0 :             CPLString osRet("\"");
    2912           0 :             osRet += CPLString(poNode->string_value)
    2913           0 :                          .replaceAll('\\', "\\\\")
    2914           0 :                          .replaceAll('"', "\\\"");
    2915           0 :             osRet += '"';
    2916           0 :             return osRet;
    2917             :         }
    2918           0 :         if (poNode->field_type == SWQ_INTEGER ||
    2919           0 :             poNode->field_type == SWQ_INTEGER64)
    2920             :         {
    2921           0 :             return CPLSPrintf(CPL_FRMT_GIB, poNode->int_value);
    2922             :         }
    2923           0 :         if (poNode->field_type == SWQ_FLOAT)
    2924             :         {
    2925           0 :             return CPLSPrintf("%.16g", poNode->float_value);
    2926             :         }
    2927           0 :         if (poNode->field_type == SWQ_TIMESTAMP)
    2928             :         {
    2929           0 :             int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
    2930           0 :                 nSecond = 0;
    2931             :             const int nDateComponents =
    2932           0 :                 OGRWF3ParseDateTime(poNode->string_value, nYear, nMonth, nDay,
    2933             :                                     nHour, nMinute, nSecond);
    2934           0 :             if (nDateComponents >= 3)
    2935             :             {
    2936             :                 CPLString osDT(SerializeDateTime(nDateComponents, nYear, nMonth,
    2937             :                                                  nDay, nHour, nMinute,
    2938           0 :                                                  nSecond));
    2939           0 :                 CPLString osRet("\"");
    2940           0 :                 osRet += osDT;
    2941           0 :                 osRet += '"';
    2942           0 :                 return osRet;
    2943             :             }
    2944             :         }
    2945             :     }
    2946             : 
    2947           0 :     m_bFilterMustBeClientSideEvaluated = true;
    2948           0 :     return CPLString();
    2949             : }
    2950             : 
    2951             : /************************************************************************/
    2952             : /*                        GetQueryableAttributes()                      */
    2953             : /************************************************************************/
    2954             : 
    2955           7 : void OGROAPIFLayer::GetQueryableAttributes()
    2956             : {
    2957           7 :     if (m_bGotQueryableAttributes)
    2958           6 :         return;
    2959           1 :     m_bGotQueryableAttributes = true;
    2960           1 :     CPLJSONDocument oAPIDoc = m_poDS->GetAPIDoc();
    2961           1 :     if (oAPIDoc.GetRoot().GetString("openapi").empty())
    2962           0 :         return;
    2963             : 
    2964           3 :     CPLJSONObject oPaths = oAPIDoc.GetRoot().GetObj("paths");
    2965             :     CPLJSONArray oParameters =
    2966           3 :         oPaths.GetObj(m_osPath).GetObj("get").GetArray("parameters");
    2967           1 :     if (!oParameters.IsValid())
    2968             :     {
    2969           0 :         oParameters = oPaths.GetObj("/collections/{collectionId}/items")
    2970           0 :                           .GetObj("get")
    2971           0 :                           .GetArray("parameters");
    2972             :     }
    2973           3 :     for (int i = 0; i < oParameters.Size(); i++)
    2974             :     {
    2975           4 :         CPLJSONObject oParam = oParameters[i];
    2976           6 :         CPLString osRef = oParam.GetString("$ref");
    2977           2 :         if (!osRef.empty() && osRef.find("#/") == 0)
    2978             :         {
    2979           0 :             oParam = oAPIDoc.GetRoot().GetObj(osRef.substr(2));
    2980             :         }
    2981           2 :         if (oParam.GetString("in") == "query")
    2982             :         {
    2983           6 :             const auto osName(oParam.GetString("name"));
    2984           2 :             if (osName == "filter-lang")
    2985             :             {
    2986           0 :                 const auto oEnums = oParam.GetObj("schema").GetArray("enum");
    2987           0 :                 for (int j = 0; j < oEnums.Size(); j++)
    2988             :                 {
    2989           0 :                     if (oEnums[j].ToString() == "cql-text")
    2990             :                     {
    2991           0 :                         m_bHasCQLText = true;
    2992           0 :                         CPLDebug("OAPIF", "CQL text detected");
    2993             :                     }
    2994           0 :                     else if (oEnums[j].ToString() == "json-filter-expr")
    2995             :                     {
    2996           0 :                         m_bHasJSONFilterExpression = true;
    2997           0 :                         CPLDebug("OAPIF", "JSON Filter expression detected");
    2998             :                     }
    2999             :                 }
    3000             :             }
    3001           2 :             else if (GetLayerDefn()->GetFieldIndex(osName.c_str()) >= 0)
    3002             :             {
    3003           2 :                 m_aoSetQueryableAttributes.insert(osName);
    3004             :             }
    3005             :         }
    3006             :     }
    3007             : 
    3008             :     // HACK
    3009           1 :     if (CPLTestBool(CPLGetConfigOption("OGR_OAPIF_ALLOW_CQL_TEXT", "NO")))
    3010           0 :         m_bHasCQLText = true;
    3011             : 
    3012           1 :     if (m_bHasCQLText || m_bHasJSONFilterExpression)
    3013             :     {
    3014           0 :         if (!m_osQueryablesURL.empty())
    3015             :         {
    3016           0 :             CPLJSONDocument oDoc;
    3017           0 :             if (m_poDS->DownloadJSon(m_osQueryablesURL, oDoc))
    3018             :             {
    3019           0 :                 auto oQueryables = oDoc.GetRoot().GetArray("queryables");
    3020           0 :                 for (int i = 0; i < oQueryables.Size(); i++)
    3021             :                 {
    3022           0 :                     const auto osId = oQueryables[i].GetString("id");
    3023           0 :                     if (!osId.empty())
    3024             :                     {
    3025           0 :                         m_aoSetQueryableAttributes.insert(osId);
    3026             :                     }
    3027             :                 }
    3028             :             }
    3029             :         }
    3030             :     }
    3031             : }
    3032             : 
    3033             : /************************************************************************/
    3034             : /*                         SetAttributeFilter()                         */
    3035             : /************************************************************************/
    3036             : 
    3037           9 : OGRErr OGROAPIFLayer::SetAttributeFilter(const char *pszQuery)
    3038             : 
    3039             : {
    3040           9 :     if (m_poAttrQuery == nullptr && pszQuery == nullptr)
    3041           1 :         return OGRERR_NONE;
    3042             : 
    3043           8 :     if (!m_bFeatureDefnEstablished)
    3044           1 :         EstablishFeatureDefn();
    3045             : 
    3046           8 :     OGRErr eErr = OGRLayer::SetAttributeFilter(pszQuery);
    3047             : 
    3048           8 :     m_osAttributeFilter.clear();
    3049           8 :     m_bFilterMustBeClientSideEvaluated = false;
    3050           8 :     m_osGetID.clear();
    3051           8 :     if (m_poAttrQuery != nullptr)
    3052             :     {
    3053           7 :         GetQueryableAttributes();
    3054             : 
    3055           7 :         swq_expr_node *poNode = (swq_expr_node *)m_poAttrQuery->GetSWQExpr();
    3056             : 
    3057           7 :         poNode->ReplaceBetweenByGEAndLERecurse();
    3058             : 
    3059           7 :         if (m_bHasCQLText)
    3060             :         {
    3061           0 :             m_osAttributeFilter = BuildFilterCQLText(poNode);
    3062           0 :             if (!m_osAttributeFilter.empty())
    3063             :             {
    3064             :                 char *pszEscaped =
    3065           0 :                     CPLEscapeString(m_osAttributeFilter, -1, CPLES_URL);
    3066           0 :                 m_osAttributeFilter = "filter=";
    3067           0 :                 m_osAttributeFilter += pszEscaped;
    3068           0 :                 m_osAttributeFilter += "&filter-lang=cql-text";
    3069           0 :                 CPLFree(pszEscaped);
    3070             :             }
    3071             :         }
    3072           7 :         else if (m_bHasJSONFilterExpression)
    3073             :         {
    3074           0 :             m_osAttributeFilter = BuildFilterJSONFilterExpr(poNode);
    3075           0 :             if (!m_osAttributeFilter.empty())
    3076             :             {
    3077             :                 char *pszEscaped =
    3078           0 :                     CPLEscapeString(m_osAttributeFilter, -1, CPLES_URL);
    3079           0 :                 m_osAttributeFilter = "filter=";
    3080           0 :                 m_osAttributeFilter += pszEscaped;
    3081           0 :                 m_osAttributeFilter += "&filter-lang=json-filter-expr";
    3082           0 :                 CPLFree(pszEscaped);
    3083             :             }
    3084             :         }
    3085             :         else
    3086             :         {
    3087           7 :             m_osAttributeFilter = BuildFilter(poNode);
    3088             :         }
    3089           7 :         if (m_osAttributeFilter.empty())
    3090             :         {
    3091           2 :             CPLDebug("OAPIF", "Full filter will be evaluated on client side.");
    3092             :         }
    3093           5 :         else if (m_bFilterMustBeClientSideEvaluated)
    3094             :         {
    3095           1 :             CPLDebug(
    3096             :                 "OAPIF",
    3097             :                 "Only part of the filter will be evaluated on server side.");
    3098             :         }
    3099             :     }
    3100             : 
    3101           8 :     ResetReading();
    3102             : 
    3103           8 :     return eErr;
    3104             : }
    3105             : 
    3106             : /************************************************************************/
    3107             : /*                              TestCapability()                        */
    3108             : /************************************************************************/
    3109             : 
    3110          11 : int OGROAPIFLayer::TestCapability(const char *pszCap)
    3111             : {
    3112          11 :     if (EQUAL(pszCap, OLCFastFeatureCount))
    3113             :     {
    3114           3 :         return m_nTotalFeatureCount >= 0 && m_poFilterGeom == nullptr &&
    3115           3 :                m_poAttrQuery == nullptr;
    3116             :     }
    3117           9 :     if (EQUAL(pszCap, OLCFastGetExtent))
    3118             :     {
    3119           1 :         return m_oOriginalExtent.IsInit();
    3120             :     }
    3121           8 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
    3122             :     {
    3123           7 :         return TRUE;
    3124             :     }
    3125             :     // Don't advertise OLCRandomRead as it requires a GET per feature
    3126           1 :     return FALSE;
    3127             : }
    3128             : 
    3129             : /************************************************************************/
    3130             : /*                                Open()                                */
    3131             : /************************************************************************/
    3132             : 
    3133          39 : static GDALDataset *OGROAPIFDriverOpen(GDALOpenInfo *poOpenInfo)
    3134             : 
    3135             : {
    3136          39 :     if (!OGROAPIFDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
    3137           0 :         return nullptr;
    3138          78 :     auto poDataset = std::make_unique<OGROAPIFDataset>();
    3139          39 :     if (!poDataset->Open(poOpenInfo))
    3140           8 :         return nullptr;
    3141          31 :     return poDataset.release();
    3142             : }
    3143             : 
    3144             : /************************************************************************/
    3145             : /*                           RegisterOGROAPIF()                         */
    3146             : /************************************************************************/
    3147             : 
    3148        1520 : void RegisterOGROAPIF()
    3149             : 
    3150             : {
    3151        1520 :     if (GDALGetDriverByName("OAPIF") != nullptr)
    3152         301 :         return;
    3153             : 
    3154        1219 :     GDALDriver *poDriver = new GDALDriver();
    3155             : 
    3156        1219 :     poDriver->SetDescription("OAPIF");
    3157        1219 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    3158        1219 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGC API - Features");
    3159        1219 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/oapif.html");
    3160             : 
    3161        1219 :     poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "OAPIF:");
    3162        1219 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
    3163             : 
    3164        1219 :     poDriver->SetMetadataItem(
    3165             :         GDAL_DMD_OPENOPTIONLIST,
    3166             :         "<OpenOptionList>"
    3167             :         "  <Option name='URL' type='string' "
    3168             :         "description='URL to the landing page or a /collections/{id}' "
    3169             :         "required='true'/>"
    3170             :         "  <Option name='PAGE_SIZE' type='int' "
    3171             :         "description='Maximum number of features to retrieve in a single "
    3172             :         "request'/>"
    3173             :         "  <Option name='INITIAL_REQUEST_PAGE_SIZE' type='int' "
    3174             :         "description='Maximum number of features to retrieve in the initial "
    3175             :         "request issued to determine the schema from a feature sample'/>"
    3176             :         "  <Option name='USERPWD' type='string' "
    3177             :         "description='Basic authentication as username:password'/>"
    3178             :         "  <Option name='IGNORE_SCHEMA' type='boolean' "
    3179             :         "description='Whether the XML Schema or JSON Schema should be ignored' "
    3180             :         "default='NO'/>"
    3181             :         "  <Option name='CRS' type='string' "
    3182             :         "description='CRS identifier to use for layers'/>"
    3183             :         "  <Option name='PREFERRED_CRS' type='string' "
    3184             :         "description='Preferred CRS identifier to use for layers'/>"
    3185             :         "  <Option name='SERVER_FEATURE_AXIS_ORDER' type='string-select' "
    3186             :         "description='Coordinate axis order of GeoJSON features returned by "
    3187             :         "the server' "
    3188             :         "default='AUTHORITY_COMPLIANT'>"
    3189             :         "    <Value>AUTHORITY_COMPLIANT</Value>"
    3190             :         "    <Value>GIS_FRIENDLY</Value>"
    3191             :         "  </Option>"
    3192        1219 :         "</OpenOptionList>");
    3193             : 
    3194        1219 :     poDriver->pfnIdentify = OGROAPIFDriverIdentify;
    3195        1219 :     poDriver->pfnOpen = OGROAPIFDriverOpen;
    3196             : 
    3197        1219 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    3198             : }

Generated by: LCOV version 1.14