LCOV - code coverage report
Current view: top level - frmts/ogcapi - gdalogcapidataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 918 1313 69.9 %
Date: 2026-02-12 23:49:34 Functions: 56 70 80.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  OGC API interface
       5             :  * Author:   Even Rouault, <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_error.h"
      14             : #include "cpl_json.h"
      15             : #include "cpl_http.h"
      16             : #include "gdal_frmts.h"
      17             : #include "gdal_priv.h"
      18             : #include "tilematrixset.hpp"
      19             : #include "gdal_utils.h"
      20             : #include "ogrsf_frmts.h"
      21             : #include "ogr_spatialref.h"
      22             : #include "gdalplugindriverproxy.h"
      23             : 
      24             : #include "parsexsd.h"
      25             : 
      26             : #include <algorithm>
      27             : #include <memory>
      28             : #include <vector>
      29             : 
      30             : #define MEDIA_TYPE_OAPI_3_0 "application/vnd.oai.openapi+json;version=3.0"
      31             : #define MEDIA_TYPE_OAPI_3_0_ALT "application/openapi+json;version=3.0"
      32             : #define MEDIA_TYPE_JSON "application/json"
      33             : #define MEDIA_TYPE_GEOJSON "application/geo+json"
      34             : #define MEDIA_TYPE_TEXT_XML "text/xml"
      35             : #define MEDIA_TYPE_APPLICATION_XML "application/xml"
      36             : #define MEDIA_TYPE_JSON_SCHEMA "application/schema+json"
      37             : 
      38             : /************************************************************************/
      39             : /* ==================================================================== */
      40             : /*                           OGCAPIDataset                              */
      41             : /* ==================================================================== */
      42             : /************************************************************************/
      43             : 
      44             : class OGCAPIDataset final : public GDALDataset
      45             : {
      46             :     friend class OGCAPIMapWrapperBand;
      47             :     friend class OGCAPITilesWrapperBand;
      48             :     friend class OGCAPITiledLayer;
      49             : 
      50             :     bool m_bMustCleanPersistent = false;
      51             :     CPLString m_osRootURL{};
      52             :     CPLString m_osUserPwd{};
      53             :     CPLString m_osUserQueryParams{};
      54             :     GDALGeoTransform m_gt{};
      55             : 
      56             :     OGRSpatialReference m_oSRS{};
      57             :     CPLString m_osTileData{};
      58             : 
      59             :     // Classic OGC API features /items access
      60             :     std::unique_ptr<GDALDataset> m_poOAPIFDS{};
      61             : 
      62             :     // Map API
      63             :     std::unique_ptr<GDALDataset> m_poWMSDS{};
      64             : 
      65             :     // Tiles API
      66             :     std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsElementary{};
      67             :     std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsAssembled{};
      68             :     std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsCropped{};
      69             : 
      70             :     std::vector<std::unique_ptr<OGRLayer>> m_apoLayers{};
      71             : 
      72             :     CPLString BuildURL(const std::string &href) const;
      73             :     void SetRootURLFromURL(const std::string &osURL);
      74             :     int FigureBands(const std::string &osContentType,
      75             :                     const CPLString &osImageURL);
      76             : 
      77             :     bool InitFromFile(GDALOpenInfo *poOpenInfo);
      78             :     bool InitFromURL(GDALOpenInfo *poOpenInfo);
      79             :     bool ProcessScale(const CPLJSONObject &oScaleDenominator,
      80             :                       const double dfXMin, const double dfYMin,
      81             :                       const double dfXMax, const double dfYMax);
      82             :     bool InitFromCollection(GDALOpenInfo *poOpenInfo, CPLJSONDocument &oDoc);
      83             :     bool Download(const CPLString &osURL, const char *pszPostContent,
      84             :                   const char *pszAccept, CPLString &osResult,
      85             :                   CPLString &osContentType, bool bEmptyContentOK,
      86             :                   CPLStringList *paosHeaders);
      87             : 
      88             :     bool DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
      89             :                       const char *pszPostContent = nullptr,
      90             :                       const char *pszAccept = MEDIA_TYPE_GEOJSON
      91             :                       ", " MEDIA_TYPE_JSON,
      92             :                       CPLStringList *paosHeaders = nullptr);
      93             : 
      94             :     std::unique_ptr<GDALDataset>
      95             :     OpenTile(const CPLString &osURLPattern, int nMatrix, int nColumn, int nRow,
      96             :              bool &bEmptyContent, unsigned int nOpenTileFlags = 0,
      97             :              const CPLString &osPrefix = {},
      98             :              const char *const *papszOpenOptions = nullptr);
      99             : 
     100             :     bool InitWithMapAPI(GDALOpenInfo *poOpenInfo,
     101             :                         const CPLJSONObject &oCollection, double dfXMin,
     102             :                         double dfYMin, double dfXMax, double dfYMax);
     103             :     bool InitWithTilesAPI(GDALOpenInfo *poOpenInfo, const CPLString &osTilesURL,
     104             :                           bool bIsMap, double dfXMin, double dfYMin,
     105             :                           double dfXMax, double dfYMax, bool bBBOXIsInCRS84,
     106             :                           const CPLJSONObject &oJsonCollection);
     107             :     bool InitWithCoverageAPI(GDALOpenInfo *poOpenInfo,
     108             :                              const CPLString &osTilesURL, double dfXMin,
     109             :                              double dfYMin, double dfXMax, double dfYMax,
     110             :                              const CPLJSONObject &oJsonCollection);
     111             : 
     112             :   protected:
     113             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     114             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     115             :                      GDALDataType eBufType, int nBandCount,
     116             :                      BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     117             :                      GSpacing nLineSpace, GSpacing nBandSpace,
     118             :                      GDALRasterIOExtraArg *psExtraArg) override;
     119             : 
     120             :     int CloseDependentDatasets() override;
     121             : 
     122             :   public:
     123          35 :     OGCAPIDataset() = default;
     124             :     ~OGCAPIDataset() override;
     125             : 
     126             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     127             :     const OGRSpatialReference *GetSpatialRef() const override;
     128             : 
     129          32 :     int GetLayerCount() const override
     130             :     {
     131          62 :         return m_poOAPIFDS ? m_poOAPIFDS->GetLayerCount()
     132          94 :                            : static_cast<int>(m_apoLayers.size());
     133             :     }
     134             : 
     135          17 :     const OGRLayer *GetLayer(int idx) const override
     136             :     {
     137          32 :         return m_poOAPIFDS                         ? m_poOAPIFDS->GetLayer(idx)
     138          15 :                : idx >= 0 && idx < GetLayerCount() ? m_apoLayers[idx].get()
     139          34 :                                                    : nullptr;
     140             :     }
     141             : 
     142             :     static int Identify(GDALOpenInfo *poOpenInfo);
     143             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
     144             : };
     145             : 
     146             : /************************************************************************/
     147             : /* ==================================================================== */
     148             : /*                      OGCAPIMapWrapperBand                            */
     149             : /* ==================================================================== */
     150             : /************************************************************************/
     151             : 
     152             : class OGCAPIMapWrapperBand final : public GDALRasterBand
     153             : {
     154             :   public:
     155             :     OGCAPIMapWrapperBand(OGCAPIDataset *poDS, int nBand);
     156             : 
     157             :     GDALRasterBand *GetOverview(int nLevel) override;
     158             :     int GetOverviewCount() override;
     159             :     GDALColorInterp GetColorInterpretation() override;
     160             : 
     161             :   protected:
     162             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
     163             :     CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
     164             :                      GDALDataType, GSpacing, GSpacing,
     165             :                      GDALRasterIOExtraArg *psExtraArg) override;
     166             : };
     167             : 
     168             : /************************************************************************/
     169             : /* ==================================================================== */
     170             : /*                     OGCAPITilesWrapperBand                           */
     171             : /* ==================================================================== */
     172             : /************************************************************************/
     173             : 
     174             : class OGCAPITilesWrapperBand final : public GDALRasterBand
     175             : {
     176             :   public:
     177             :     OGCAPITilesWrapperBand(OGCAPIDataset *poDS, int nBand);
     178             : 
     179             :     GDALRasterBand *GetOverview(int nLevel) override;
     180             :     int GetOverviewCount() override;
     181             :     GDALColorInterp GetColorInterpretation() override;
     182             : 
     183             :   protected:
     184             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
     185             :     CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
     186             :                      GDALDataType, GSpacing, GSpacing,
     187             :                      GDALRasterIOExtraArg *psExtraArg) override;
     188             : };
     189             : 
     190             : /************************************************************************/
     191             : /* ==================================================================== */
     192             : /*                           OGCAPITiledLayer                           */
     193             : /* ==================================================================== */
     194             : /************************************************************************/
     195             : 
     196             : class OGCAPITiledLayer;
     197             : 
     198             : class OGCAPITiledLayerFeatureDefn final : public OGRFeatureDefn
     199             : {
     200             :     OGCAPITiledLayer *m_poLayer = nullptr;
     201             : 
     202             :     CPL_DISALLOW_COPY_ASSIGN(OGCAPITiledLayerFeatureDefn)
     203             : 
     204             :   public:
     205          35 :     OGCAPITiledLayerFeatureDefn(OGCAPITiledLayer *poLayer, const char *pszName)
     206          35 :         : OGRFeatureDefn(pszName), m_poLayer(poLayer)
     207             :     {
     208          35 :     }
     209             : 
     210             :     int GetFieldCount() const override;
     211             : 
     212          35 :     void InvalidateLayer()
     213             :     {
     214          35 :         m_poLayer = nullptr;
     215          35 :     }
     216             : };
     217             : 
     218             : class OGCAPITiledLayer final
     219             :     : public OGRLayer,
     220             :       public OGRGetNextFeatureThroughRaw<OGCAPITiledLayer>
     221             : {
     222             :     OGCAPIDataset *m_poDS = nullptr;
     223             :     bool m_bFeatureDefnEstablished = false;
     224             :     bool m_bEstablishFieldsCalled =
     225             :         false;  // prevent recursion in EstablishFields()
     226             :     OGCAPITiledLayerFeatureDefn *m_poFeatureDefn = nullptr;
     227             :     OGREnvelope m_sEnvelope{};
     228             :     std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
     229             :     OGRLayer *m_poUnderlyingLayer = nullptr;
     230             :     int m_nCurY = 0;
     231             :     int m_nCurX = 0;
     232             : 
     233             :     CPLString m_osTileURL{};
     234             :     bool m_bIsMVT = false;
     235             : 
     236             :     const gdal::TileMatrixSet::TileMatrix m_oTileMatrix{};
     237             :     bool m_bInvertAxis = false;
     238             : 
     239             :     // absolute bounds
     240             :     int m_nMinX = 0;
     241             :     int m_nMaxX = 0;
     242             :     int m_nMinY = 0;
     243             :     int m_nMaxY = 0;
     244             : 
     245             :     // depends on spatial filter
     246             :     int m_nCurMinX = 0;
     247             :     int m_nCurMaxX = 0;
     248             :     int m_nCurMinY = 0;
     249             :     int m_nCurMaxY = 0;
     250             : 
     251             :     int GetCoalesceFactorForRow(int nRow) const;
     252             :     bool IncrementTileIndices();
     253             :     OGRFeature *GetNextRawFeature();
     254             :     GDALDataset *OpenTile(int nX, int nY, bool &bEmptyContent);
     255             :     void FinalizeFeatureDefnWithLayer(OGRLayer *poUnderlyingLayer);
     256             :     OGRFeature *BuildFeature(std::unique_ptr<OGRFeature> poSrcFeature, int nX,
     257             :                              int nY);
     258             : 
     259             :     CPL_DISALLOW_COPY_ASSIGN(OGCAPITiledLayer)
     260             : 
     261             :   protected:
     262             :     friend class OGCAPITiledLayerFeatureDefn;
     263             :     void EstablishFields();
     264             : 
     265             :   public:
     266             :     OGCAPITiledLayer(OGCAPIDataset *poDS, bool bInvertAxis,
     267             :                      const CPLString &osTileURL, bool bIsMVT,
     268             :                      const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     269             :                      OGRwkbGeometryType eGeomType);
     270             :     ~OGCAPITiledLayer() override;
     271             : 
     272             :     void SetExtent(double dfXMin, double dfYMin, double dfXMax, double dfYMax);
     273             :     void SetFields(const std::vector<std::unique_ptr<OGRFieldDefn>> &apoFields);
     274             :     void SetMinMaxXY(int minCol, int minRow, int maxCol, int maxRow);
     275             : 
     276             :     void ResetReading() override;
     277             : 
     278           0 :     const OGRFeatureDefn *GetLayerDefn() const override
     279             :     {
     280           0 :         return m_poFeatureDefn;
     281             :     }
     282             : 
     283          15 :     const char *GetName() const override
     284             :     {
     285          15 :         return m_poFeatureDefn->GetName();
     286             :     }
     287             : 
     288           0 :     OGRwkbGeometryType GetGeomType() const override
     289             :     {
     290           0 :         return m_poFeatureDefn->GetGeomType();
     291             :     }
     292           5 :     DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGCAPITiledLayer)
     293             : 
     294           0 :     GIntBig GetFeatureCount(int /* bForce */) override
     295             :     {
     296           0 :         return -1;
     297             :     }
     298             : 
     299             :     OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
     300             :                       bool bForce) override;
     301             : 
     302             :     OGRErr ISetSpatialFilter(int iGeomField,
     303             :                              const OGRGeometry *poGeom) override;
     304             : 
     305             :     OGRFeature *GetFeature(GIntBig nFID) override;
     306             :     int TestCapability(const char *) const override;
     307             : };
     308             : 
     309             : /************************************************************************/
     310             : /*                           GetFieldCount()                            */
     311             : /************************************************************************/
     312             : 
     313         110 : int OGCAPITiledLayerFeatureDefn::GetFieldCount() const
     314             : {
     315         110 :     if (m_poLayer)
     316             :     {
     317         110 :         m_poLayer->EstablishFields();
     318             :     }
     319         110 :     return OGRFeatureDefn::GetFieldCount();
     320             : }
     321             : 
     322             : /************************************************************************/
     323             : /*                           ~OGCAPIDataset()                           */
     324             : /************************************************************************/
     325             : 
     326          70 : OGCAPIDataset::~OGCAPIDataset()
     327             : {
     328          35 :     if (m_bMustCleanPersistent)
     329             :     {
     330          35 :         char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
     331             :                                               CPLSPrintf("OGCAPI:%p", this));
     332          35 :         CPLHTTPDestroyResult(CPLHTTPFetch(m_osRootURL, papszOptions));
     333          35 :         CSLDestroy(papszOptions);
     334             :     }
     335             : 
     336          35 :     OGCAPIDataset::CloseDependentDatasets();
     337          70 : }
     338             : 
     339             : /************************************************************************/
     340             : /*                       CloseDependentDatasets()                       */
     341             : /************************************************************************/
     342             : 
     343          35 : int OGCAPIDataset::CloseDependentDatasets()
     344             : {
     345          35 :     if (m_apoDatasetsElementary.empty())
     346          35 :         return false;
     347             : 
     348             :     // in this order
     349           0 :     m_apoDatasetsCropped.clear();
     350           0 :     m_apoDatasetsAssembled.clear();
     351           0 :     m_apoDatasetsElementary.clear();
     352           0 :     return true;
     353             : }
     354             : 
     355             : /************************************************************************/
     356             : /*                          GetGeoTransform()                           */
     357             : /************************************************************************/
     358             : 
     359           7 : CPLErr OGCAPIDataset::GetGeoTransform(GDALGeoTransform &gt) const
     360             : {
     361           7 :     gt = m_gt;
     362           7 :     return CE_None;
     363             : }
     364             : 
     365             : /************************************************************************/
     366             : /*                           GetSpatialRef()                            */
     367             : /************************************************************************/
     368             : 
     369           3 : const OGRSpatialReference *OGCAPIDataset::GetSpatialRef() const
     370             : {
     371           3 :     return !m_oSRS.IsEmpty() ? &m_oSRS : nullptr;
     372             : }
     373             : 
     374             : /************************************************************************/
     375             : /*                          CheckContentType()                          */
     376             : /************************************************************************/
     377             : 
     378             : // We may ask for "application/openapi+json;version=3.0"
     379             : // and the server returns "application/openapi+json; charset=utf-8; version=3.0"
     380          93 : static bool CheckContentType(const char *pszGotContentType,
     381             :                              const char *pszExpectedContentType)
     382             : {
     383         186 :     CPLStringList aosGotTokens(CSLTokenizeString2(pszGotContentType, "; ", 0));
     384             :     CPLStringList aosExpectedTokens(
     385         186 :         CSLTokenizeString2(pszExpectedContentType, "; ", 0));
     386         186 :     for (int i = 0; i < aosExpectedTokens.size(); i++)
     387             :     {
     388          93 :         bool bFound = false;
     389          93 :         for (int j = 0; j < aosGotTokens.size(); j++)
     390             :         {
     391          93 :             if (EQUAL(aosExpectedTokens[i], aosGotTokens[j]))
     392             :             {
     393          93 :                 bFound = true;
     394          93 :                 break;
     395             :             }
     396             :         }
     397          93 :         if (!bFound)
     398           0 :             return false;
     399             :     }
     400          93 :     return true;
     401             : }
     402             : 
     403             : /************************************************************************/
     404             : /*                              Download()                              */
     405             : /************************************************************************/
     406             : 
     407         122 : bool OGCAPIDataset::Download(const CPLString &osURL, const char *pszPostContent,
     408             :                              const char *pszAccept, CPLString &osResult,
     409             :                              CPLString &osContentType, bool bEmptyContentOK,
     410             :                              CPLStringList *paosHeaders)
     411             : {
     412         122 :     char **papszOptions = nullptr;
     413         244 :     CPLString osHeaders;
     414         122 :     if (pszAccept)
     415             :     {
     416         101 :         osHeaders += "Accept: ";
     417         101 :         osHeaders += pszAccept;
     418             :     }
     419         122 :     if (pszPostContent)
     420             :     {
     421           0 :         if (!osHeaders.empty())
     422             :         {
     423           0 :             osHeaders += "\r\n";
     424             :         }
     425           0 :         osHeaders += "Content-Type: application/json";
     426             :     }
     427         122 :     if (!osHeaders.empty())
     428             :     {
     429             :         papszOptions =
     430         101 :             CSLSetNameValue(papszOptions, "HEADERS", osHeaders.c_str());
     431             :     }
     432         122 :     if (!m_osUserPwd.empty())
     433             :     {
     434             :         papszOptions =
     435           0 :             CSLSetNameValue(papszOptions, "USERPWD", m_osUserPwd.c_str());
     436             :     }
     437         122 :     m_bMustCleanPersistent = true;
     438             :     papszOptions =
     439         122 :         CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=OGCAPI:%p", this));
     440         244 :     CPLString osURLWithQueryParameters(osURL);
     441           0 :     if (!m_osUserQueryParams.empty() &&
     442         244 :         osURL.find('?' + m_osUserQueryParams) == std::string::npos &&
     443         122 :         osURL.find('&' + m_osUserQueryParams) == std::string::npos)
     444             :     {
     445           0 :         if (osURL.find('?') == std::string::npos)
     446             :         {
     447           0 :             osURLWithQueryParameters += '?';
     448             :         }
     449             :         else
     450             :         {
     451           0 :             osURLWithQueryParameters += '&';
     452             :         }
     453           0 :         osURLWithQueryParameters += m_osUserQueryParams;
     454             :     }
     455         122 :     if (pszPostContent)
     456             :     {
     457             :         papszOptions =
     458           0 :             CSLSetNameValue(papszOptions, "POSTFIELDS", pszPostContent);
     459             :     }
     460             :     CPLHTTPResult *psResult =
     461         122 :         CPLHTTPFetch(osURLWithQueryParameters, papszOptions);
     462         122 :     CSLDestroy(papszOptions);
     463         122 :     if (!psResult)
     464           0 :         return false;
     465             : 
     466         122 :     if (paosHeaders)
     467             :     {
     468           0 :         *paosHeaders = CSLDuplicate(psResult->papszHeaders);
     469             :     }
     470             : 
     471         122 :     if (psResult->pszErrBuf != nullptr)
     472             :     {
     473           8 :         std::string osErrorMsg(psResult->pszErrBuf);
     474           8 :         const char *pszData =
     475             :             reinterpret_cast<const char *>(psResult->pabyData);
     476           8 :         if (pszData)
     477             :         {
     478           8 :             osErrorMsg += ", ";
     479           8 :             osErrorMsg.append(pszData, CPLStrnlen(pszData, 1000));
     480             :         }
     481           8 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
     482           8 :         CPLHTTPDestroyResult(psResult);
     483           8 :         return false;
     484             :     }
     485             : 
     486         114 :     if (psResult->pszContentType)
     487         114 :         osContentType = psResult->pszContentType;
     488             : 
     489         114 :     if (pszAccept != nullptr)
     490             :     {
     491          93 :         bool bFoundExpectedContentType = false;
     492          93 :         if (strstr(pszAccept, "xml") && psResult->pszContentType != nullptr &&
     493           0 :             (CheckContentType(psResult->pszContentType, MEDIA_TYPE_TEXT_XML) ||
     494           0 :              CheckContentType(psResult->pszContentType,
     495             :                               MEDIA_TYPE_APPLICATION_XML)))
     496             :         {
     497           0 :             bFoundExpectedContentType = true;
     498             :         }
     499             : 
     500         186 :         if (strstr(pszAccept, MEDIA_TYPE_JSON_SCHEMA) &&
     501          93 :             psResult->pszContentType != nullptr &&
     502           0 :             (CheckContentType(psResult->pszContentType, MEDIA_TYPE_JSON) ||
     503           0 :              CheckContentType(psResult->pszContentType,
     504             :                               MEDIA_TYPE_JSON_SCHEMA)))
     505             :         {
     506           0 :             bFoundExpectedContentType = true;
     507             :         }
     508             : 
     509           0 :         for (const char *pszMediaType : {
     510             :                  MEDIA_TYPE_JSON,
     511             :                  MEDIA_TYPE_GEOJSON,
     512             :                  MEDIA_TYPE_OAPI_3_0,
     513          93 :              })
     514             :         {
     515         279 :             if (strstr(pszAccept, pszMediaType) &&
     516         186 :                 psResult->pszContentType != nullptr &&
     517          93 :                 CheckContentType(psResult->pszContentType, pszMediaType))
     518             :             {
     519          93 :                 bFoundExpectedContentType = true;
     520          93 :                 break;
     521             :             }
     522             :         }
     523             : 
     524          93 :         if (!bFoundExpectedContentType)
     525             :         {
     526           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unexpected Content-Type: %s",
     527           0 :                      psResult->pszContentType ? psResult->pszContentType
     528             :                                               : "(null)");
     529           0 :             CPLHTTPDestroyResult(psResult);
     530           0 :             return false;
     531             :         }
     532             :     }
     533             : 
     534         114 :     if (psResult->pabyData == nullptr)
     535             :     {
     536          15 :         osResult.clear();
     537          15 :         if (!bEmptyContentOK)
     538             :         {
     539           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     540             :                      "Empty content returned by server");
     541           0 :             CPLHTTPDestroyResult(psResult);
     542           0 :             return false;
     543             :         }
     544             :     }
     545             :     else
     546             :     {
     547          99 :         osResult.assign(reinterpret_cast<const char *>(psResult->pabyData),
     548          99 :                         psResult->nDataLen);
     549             : #ifdef DEBUG_VERBOSE
     550             :         CPLDebug("OGCAPI", "%s", osResult.c_str());
     551             : #endif
     552             :     }
     553         114 :     CPLHTTPDestroyResult(psResult);
     554         114 :     return true;
     555             : }
     556             : 
     557             : /************************************************************************/
     558             : /*                            DownloadJSon()                            */
     559             : /************************************************************************/
     560             : 
     561         101 : bool OGCAPIDataset::DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
     562             :                                  const char *pszPostContent,
     563             :                                  const char *pszAccept,
     564             :                                  CPLStringList *paosHeaders)
     565             : {
     566         202 :     CPLString osResult;
     567         202 :     CPLString osContentType;
     568         101 :     if (!Download(osURL, pszPostContent, pszAccept, osResult, osContentType,
     569             :                   false, paosHeaders))
     570           8 :         return false;
     571          93 :     return oDoc.LoadMemory(osResult);
     572             : }
     573             : 
     574             : /************************************************************************/
     575             : /*                              OpenTile()                              */
     576             : /************************************************************************/
     577             : 
     578             : std::unique_ptr<GDALDataset>
     579          21 : OGCAPIDataset::OpenTile(const CPLString &osURLPattern, int nMatrix, int nColumn,
     580             :                         int nRow, bool &bEmptyContent,
     581             :                         unsigned int nOpenTileFlags, const CPLString &osPrefix,
     582             :                         const char *const *papszOpenTileOptions)
     583             : {
     584          42 :     CPLString osURL(osURLPattern);
     585          21 :     osURL.replaceAll("{tileMatrix}", CPLSPrintf("%d", nMatrix));
     586          21 :     osURL.replaceAll("{tileCol}", CPLSPrintf("%d", nColumn));
     587          21 :     osURL.replaceAll("{tileRow}", CPLSPrintf("%d", nRow));
     588             : 
     589          42 :     CPLString osContentType;
     590          21 :     if (!this->Download(osURL, nullptr, nullptr, m_osTileData, osContentType,
     591             :                         true, nullptr))
     592             :     {
     593           0 :         return nullptr;
     594             :     }
     595             : 
     596          21 :     bEmptyContent = m_osTileData.empty();
     597          21 :     if (bEmptyContent)
     598          15 :         return nullptr;
     599             : 
     600          12 :     const CPLString osTempFile(VSIMemGenerateHiddenFilename("ogcapi"));
     601           6 :     VSIFCloseL(VSIFileFromMemBuffer(osTempFile.c_str(),
     602           6 :                                     reinterpret_cast<GByte *>(&m_osTileData[0]),
     603           6 :                                     m_osTileData.size(), false));
     604             : 
     605           6 :     GDALDataset *result = nullptr;
     606             : 
     607           6 :     if (osPrefix.empty())
     608           3 :         result = GDALDataset::Open(osTempFile.c_str(), nOpenTileFlags, nullptr,
     609             :                                    papszOpenTileOptions);
     610             :     else
     611             :         result =
     612           3 :             GDALDataset::Open((osPrefix + ":" + osTempFile).c_str(),
     613             :                               nOpenTileFlags, nullptr, papszOpenTileOptions);
     614             : 
     615           6 :     VSIUnlink(osTempFile);
     616             : 
     617           6 :     return std::unique_ptr<GDALDataset>(result);
     618             : }
     619             : 
     620             : /************************************************************************/
     621             : /*                              Identify()                              */
     622             : /************************************************************************/
     623             : 
     624       71700 : int OGCAPIDataset::Identify(GDALOpenInfo *poOpenInfo)
     625             : {
     626       71700 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:"))
     627          58 :         return TRUE;
     628       71642 :     if (poOpenInfo->IsExtensionEqualToCI("moaw"))
     629           0 :         return TRUE;
     630       71642 :     if (poOpenInfo->IsSingleAllowedDriver("OGCAPI"))
     631             :     {
     632          12 :         return TRUE;
     633             :     }
     634       71630 :     return FALSE;
     635             : }
     636             : 
     637             : /************************************************************************/
     638             : /*                              BuildURL()                              */
     639             : /************************************************************************/
     640             : 
     641        7783 : CPLString OGCAPIDataset::BuildURL(const std::string &href) const
     642             : {
     643        7783 :     if (!href.empty() && href[0] == '/')
     644           0 :         return m_osRootURL + href;
     645        7783 :     return href;
     646             : }
     647             : 
     648             : /************************************************************************/
     649             : /*                         SetRootURLFromURL()                          */
     650             : /************************************************************************/
     651             : 
     652          27 : void OGCAPIDataset::SetRootURLFromURL(const std::string &osURL)
     653             : {
     654          27 :     const char *pszStr = osURL.c_str();
     655          27 :     const char *pszPtr = pszStr;
     656          27 :     if (STARTS_WITH(pszPtr, "http://"))
     657          27 :         pszPtr += strlen("http://");
     658           0 :     else if (STARTS_WITH(pszPtr, "https://"))
     659           0 :         pszPtr += strlen("https://");
     660          27 :     pszPtr = strchr(pszPtr, '/');
     661          27 :     if (pszPtr)
     662          27 :         m_osRootURL.assign(pszStr, pszPtr - pszStr);
     663          27 : }
     664             : 
     665             : /************************************************************************/
     666             : /*                            FigureBands()                             */
     667             : /************************************************************************/
     668             : 
     669           9 : int OGCAPIDataset::FigureBands(const std::string &osContentType,
     670             :                                const CPLString &osImageURL)
     671             : {
     672           9 :     int result = 0;
     673             : 
     674           9 :     if (osContentType == "image/png")
     675             :     {
     676           6 :         result = 4;
     677             :     }
     678           3 :     else if (osContentType == "image/jpeg")
     679             :     {
     680           2 :         result = 3;
     681             :     }
     682             :     else
     683             :     {
     684             :         // Since we don't know the format download a tile and find out
     685           1 :         bool bEmptyContent = false;
     686             :         std::unique_ptr<GDALDataset> dataset =
     687           1 :             OpenTile(osImageURL, 0, 0, 0, bEmptyContent, GDAL_OF_RASTER);
     688             : 
     689             :         // Return the bands from the image, if we didn't get an image then assume 3.
     690           1 :         result = dataset ? static_cast<int>(dataset->GetBands().size()) : 3;
     691             :     }
     692             : 
     693           9 :     return result;
     694             : }
     695             : 
     696             : /************************************************************************/
     697             : /*                            InitFromFile()                            */
     698             : /************************************************************************/
     699             : 
     700           0 : bool OGCAPIDataset::InitFromFile(GDALOpenInfo *poOpenInfo)
     701             : {
     702           0 :     CPLJSONDocument oDoc;
     703           0 :     if (!oDoc.Load(poOpenInfo->pszFilename))
     704           0 :         return false;
     705           0 :     auto oProcess = oDoc.GetRoot()["process"];
     706           0 :     if (oProcess.GetType() != CPLJSONObject::Type::String)
     707             :     {
     708           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     709             :                  "Cannot find 'process' key in .moaw file");
     710           0 :         return false;
     711             :     }
     712             : 
     713           0 :     const CPLString osURLProcess(oProcess.ToString());
     714           0 :     SetRootURLFromURL(osURLProcess);
     715             : 
     716           0 :     GByte *pabyContent = nullptr;
     717           0 :     vsi_l_offset nSize = 0;
     718           0 :     if (!VSIIngestFile(poOpenInfo->fpL, nullptr, &pabyContent, &nSize,
     719             :                        1024 * 1024))
     720           0 :         return false;
     721           0 :     CPLString osPostContent(reinterpret_cast<const char *>(pabyContent));
     722           0 :     CPLFree(pabyContent);
     723           0 :     if (!DownloadJSon(osURLProcess.c_str(), oDoc, osPostContent.c_str()))
     724           0 :         return false;
     725             : 
     726           0 :     return InitFromCollection(poOpenInfo, oDoc);
     727             : }
     728             : 
     729             : /************************************************************************/
     730             : /*                            ProcessScale()                            */
     731             : /************************************************************************/
     732             : 
     733          17 : bool OGCAPIDataset::ProcessScale(const CPLJSONObject &oScaleDenominator,
     734             :                                  const double dfXMin, const double dfYMin,
     735             :                                  const double dfXMax, const double dfYMax)
     736             : 
     737             : {
     738          17 :     double dfRes = 1e-8;  // arbitrary
     739          17 :     if (oScaleDenominator.IsValid())
     740             :     {
     741           0 :         const double dfScaleDenominator = oScaleDenominator.ToDouble();
     742           0 :         constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
     743           0 :         dfRes = dfScaleDenominator / ((HALF_CIRCUMFERENCE / 180) / 0.28e-3);
     744             :     }
     745          17 :     if (dfRes == 0.0)
     746           0 :         return false;
     747             : 
     748          17 :     double dfXSize = (dfXMax - dfXMin) / dfRes;
     749          17 :     double dfYSize = (dfYMax - dfYMin) / dfRes;
     750          97 :     while (dfXSize > INT_MAX || dfYSize > INT_MAX)
     751             :     {
     752          80 :         dfXSize /= 2;
     753          80 :         dfYSize /= 2;
     754             :     }
     755             : 
     756          17 :     nRasterXSize = std::max(1, static_cast<int>(0.5 + dfXSize));
     757          17 :     nRasterYSize = std::max(1, static_cast<int>(0.5 + dfYSize));
     758          17 :     m_gt.xorig = dfXMin;
     759          17 :     m_gt.xscale = (dfXMax - dfXMin) / nRasterXSize;
     760          17 :     m_gt.yorig = dfYMax;
     761          17 :     m_gt.yscale = -(dfYMax - dfYMin) / nRasterYSize;
     762             : 
     763          17 :     return true;
     764             : }
     765             : 
     766             : /************************************************************************/
     767             : /*                         InitFromCollection()                         */
     768             : /************************************************************************/
     769             : 
     770          17 : bool OGCAPIDataset::InitFromCollection(GDALOpenInfo *poOpenInfo,
     771             :                                        CPLJSONDocument &oDoc)
     772             : {
     773          34 :     const CPLJSONObject oRoot = oDoc.GetRoot();
     774          51 :     auto osTitle = oRoot.GetString("title");
     775          17 :     if (!osTitle.empty())
     776             :     {
     777          17 :         SetMetadataItem("TITLE", osTitle.c_str());
     778             :     }
     779             : 
     780          51 :     auto oLinks = oRoot.GetArray("links");
     781          17 :     if (!oLinks.IsValid())
     782             :     {
     783           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing links");
     784           0 :         return false;
     785             :     }
     786          51 :     auto oBboxes = oRoot["extent"]["spatial"]["bbox"].ToArray();
     787          17 :     if (oBboxes.Size() != 1)
     788             :     {
     789           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing bbox");
     790           0 :         return false;
     791             :     }
     792          34 :     auto oBbox = oBboxes[0].ToArray();
     793          17 :     if (oBbox.Size() != 4)
     794             :     {
     795           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid bbox");
     796           0 :         return false;
     797             :     }
     798             :     const bool bBBOXIsInCRS84 =
     799          17 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MINX") == nullptr;
     800             :     const double dfXMin =
     801          17 :         CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MINX",
     802             :                                      CPLSPrintf("%.17g", oBbox[0].ToDouble())));
     803             :     const double dfYMin =
     804          17 :         CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MINY",
     805             :                                      CPLSPrintf("%.17g", oBbox[1].ToDouble())));
     806             :     const double dfXMax =
     807          17 :         CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAXX",
     808             :                                      CPLSPrintf("%.17g", oBbox[2].ToDouble())));
     809             :     const double dfYMax =
     810          17 :         CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAXY",
     811             :                                      CPLSPrintf("%.17g", oBbox[3].ToDouble())));
     812             : 
     813          51 :     auto oScaleDenominator = oRoot["scaleDenominator"];
     814             : 
     815          17 :     if (!ProcessScale(oScaleDenominator, dfXMin, dfYMin, dfXMax, dfYMax))
     816           0 :         return false;
     817             : 
     818          17 :     bool bFoundMap = false;
     819             : 
     820          34 :     CPLString osTilesetsMapURL;
     821          17 :     bool bTilesetsMapURLJson = false;
     822             : 
     823          34 :     CPLString osTilesetsVectorURL;
     824          17 :     bool bTilesetsVectorURLJson = false;
     825             : 
     826          34 :     CPLString osCoverageURL;
     827          17 :     bool bCoverageGeotiff = false;
     828             : 
     829          34 :     CPLString osItemsURL;
     830          17 :     bool bItemsJson = false;
     831             : 
     832          34 :     CPLString osSelfURL;
     833          17 :     bool bSelfJson = false;
     834             : 
     835         530 :     for (const auto &oLink : oLinks)
     836             :     {
     837        1539 :         const auto osRel = oLink.GetString("rel");
     838        1539 :         const auto osType = oLink.GetString("type");
     839         975 :         if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/map" ||
     840        1060 :              osRel == "[ogc-rel:map]") &&
     841          85 :             (osType == "image/png" || osType == "image/jpeg"))
     842             :         {
     843          34 :             bFoundMap = true;
     844             :         }
     845        1258 :         else if (!bTilesetsMapURLJson &&
     846         399 :                  (osRel ==
     847         380 :                       "http://www.opengis.net/def/rel/ogc/1.0/tilesets-map" ||
     848         380 :                   osRel == "[ogc-rel:tilesets-map]"))
     849             :         {
     850          19 :             if (osType == MEDIA_TYPE_JSON)
     851             :             {
     852          16 :                 bTilesetsMapURLJson = true;
     853          16 :                 osTilesetsMapURL = BuildURL(oLink["href"].ToString());
     854             :             }
     855           3 :             else if (osType.empty())
     856             :             {
     857           1 :                 osTilesetsMapURL = BuildURL(oLink["href"].ToString());
     858             :             }
     859             :         }
     860        1241 :         else if (!bTilesetsVectorURLJson &&
     861         396 :                  (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
     862         385 :                            "tilesets-vector" ||
     863         385 :                   osRel == "[ogc-rel:tilesets-vector]"))
     864             :         {
     865          11 :             if (osType == MEDIA_TYPE_JSON)
     866             :             {
     867           8 :                 bTilesetsVectorURLJson = true;
     868           8 :                 osTilesetsVectorURL = BuildURL(oLink["href"].ToString());
     869             :             }
     870           3 :             else if (osType.empty())
     871             :             {
     872           1 :                 osTilesetsVectorURL = BuildURL(oLink["href"].ToString());
     873             :             }
     874             :         }
     875         882 :         else if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/coverage" ||
     876         906 :                   osRel == "[ogc-rel:coverage]") &&
     877          24 :                  (osType == "image/tiff; application=geotiff" ||
     878           8 :                   osType == "application/x-geotiff"))
     879             :         {
     880           8 :             if (!bCoverageGeotiff)
     881             :             {
     882           8 :                 osCoverageURL = BuildURL(oLink["href"].ToString());
     883           8 :                 bCoverageGeotiff = true;
     884             :             }
     885             :         }
     886         874 :         else if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/coverage" ||
     887         882 :                   osRel == "[ogc-rel:coverage]") &&
     888           8 :                  osType.empty())
     889             :         {
     890           0 :             osCoverageURL = BuildURL(oLink["href"].ToString());
     891             :         }
     892         441 :         else if (!bItemsJson && osRel == "items")
     893             :         {
     894           9 :             if (osType == MEDIA_TYPE_GEOJSON || osType == MEDIA_TYPE_JSON)
     895             :             {
     896           9 :                 bItemsJson = true;
     897           9 :                 osItemsURL = BuildURL(oLink["href"].ToString());
     898             :             }
     899           0 :             else if (osType.empty())
     900             :             {
     901           0 :                 osItemsURL = BuildURL(oLink["href"].ToString());
     902             :             }
     903             :         }
     904         432 :         else if (!bSelfJson && osRel == "self")
     905             :         {
     906          17 :             if (osType == "application/json")
     907             :             {
     908          16 :                 bSelfJson = true;
     909          16 :                 osSelfURL = BuildURL(oLink["href"].ToString());
     910             :             }
     911           1 :             else if (osType.empty())
     912             :             {
     913           1 :                 osSelfURL = BuildURL(oLink["href"].ToString());
     914             :             }
     915             :         }
     916             :     }
     917             : 
     918           0 :     if (!bFoundMap && osTilesetsMapURL.empty() && osTilesetsVectorURL.empty() &&
     919          17 :         osCoverageURL.empty() && osSelfURL.empty() && osItemsURL.empty())
     920             :     {
     921           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     922             :                  "Missing map, tilesets, coverage or items relation in links");
     923           0 :         return false;
     924             :     }
     925             : 
     926             :     const char *pszAPI =
     927          17 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "API", "AUTO");
     928          18 :     if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "COVERAGE")) &&
     929           1 :         !osCoverageURL.empty())
     930             :     {
     931           1 :         return InitWithCoverageAPI(poOpenInfo, osCoverageURL, dfXMin, dfYMin,
     932           2 :                                    dfXMax, dfYMax, oDoc.GetRoot());
     933             :     }
     934          29 :     else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "TILES")) &&
     935          13 :              (!osTilesetsMapURL.empty() || !osTilesetsVectorURL.empty()))
     936             :     {
     937          13 :         bool bRet = false;
     938          13 :         if (!osTilesetsMapURL.empty())
     939          13 :             bRet = InitWithTilesAPI(poOpenInfo, osTilesetsMapURL, true, dfXMin,
     940             :                                     dfYMin, dfXMax, dfYMax, bBBOXIsInCRS84,
     941          26 :                                     oDoc.GetRoot());
     942          13 :         if (!bRet && !osTilesetsVectorURL.empty())
     943           5 :             bRet = InitWithTilesAPI(poOpenInfo, osTilesetsVectorURL, false,
     944             :                                     dfXMin, dfYMin, dfXMax, dfYMax,
     945          10 :                                     bBBOXIsInCRS84, oDoc.GetRoot());
     946          13 :         return bRet;
     947             :     }
     948           3 :     else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "MAP")) && bFoundMap)
     949             :     {
     950           1 :         return InitWithMapAPI(poOpenInfo, oRoot, dfXMin, dfYMin, dfXMax,
     951           1 :                               dfYMax);
     952             :     }
     953           2 :     else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "ITEMS")) &&
     954           6 :              !osSelfURL.empty() && !osItemsURL.empty() &&
     955           2 :              (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
     956             :     {
     957           4 :         m_poOAPIFDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     958           6 :             ("OAPIF_COLLECTION:" + osSelfURL).c_str(), GDAL_OF_VECTOR));
     959           2 :         if (m_poOAPIFDS)
     960           2 :             return true;
     961             :     }
     962             : 
     963           0 :     CPLError(CE_Failure, CPLE_AppDefined, "API %s requested, but not available",
     964             :              pszAPI);
     965           0 :     return false;
     966             : }
     967             : 
     968             : /************************************************************************/
     969             : /*                            InitFromURL()                             */
     970             : /************************************************************************/
     971             : 
     972          35 : bool OGCAPIDataset::InitFromURL(GDALOpenInfo *poOpenInfo)
     973             : {
     974           6 :     const char *pszInitialURL =
     975          35 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:")
     976          29 :             ? poOpenInfo->pszFilename + strlen("OGCAPI:")
     977             :             : poOpenInfo->pszFilename;
     978          70 :     CPLJSONDocument oDoc;
     979          70 :     CPLString osURL(pszInitialURL);
     980          35 :     if (!DownloadJSon(osURL, oDoc))
     981           8 :         return false;
     982             : 
     983          27 :     SetRootURLFromURL(osURL);
     984             : 
     985          81 :     auto oCollections = oDoc.GetRoot().GetArray("collections");
     986          27 :     if (!oCollections.IsValid())
     987             :     {
     988          27 :         if (!oDoc.GetRoot().GetArray("extent").IsValid())
     989             :         {
     990             :             // If there is no "collections" or "extent" member, then it is
     991             :             // perhaps a landing page
     992          54 :             const auto oLinks = oDoc.GetRoot().GetArray("links");
     993          27 :             osURL.clear();
     994         628 :             for (const auto &oLink : oLinks)
     995             :             {
     996        1214 :                 if (oLink["rel"].ToString() == "data" &&
     997         613 :                     oLink["type"].ToString() == MEDIA_TYPE_JSON)
     998             :                 {
     999           9 :                     osURL = BuildURL(oLink["href"].ToString());
    1000           9 :                     break;
    1001             :                 }
    1002        1187 :                 else if (oLink["rel"].ToString() == "data" &&
    1003         595 :                          !oLink.GetObj("type").IsValid())
    1004             :                 {
    1005           1 :                     osURL = BuildURL(oLink["href"].ToString());
    1006             :                 }
    1007             :             }
    1008          27 :             if (!osURL.empty())
    1009             :             {
    1010          10 :                 if (!DownloadJSon(osURL, oDoc))
    1011           0 :                     return false;
    1012          10 :                 oCollections = oDoc.GetRoot().GetArray("collections");
    1013             :             }
    1014             :         }
    1015             : 
    1016          27 :         if (!oCollections.IsValid())
    1017             :         {
    1018             :             // This is hopefully a /collections/{id} response
    1019          17 :             return InitFromCollection(poOpenInfo, oDoc);
    1020             :         }
    1021             :     }
    1022             : 
    1023             :     // This is a /collections response
    1024          10 :     CPLStringList aosSubdatasets;
    1025        7500 :     for (const auto &oCollection : oCollections)
    1026             :     {
    1027       14980 :         const auto osTitle = oCollection.GetString("title");
    1028       14980 :         const auto osLayerDataType = oCollection.GetString("layerDataType");
    1029             :         // CPLDebug("OGCAPI", "%s: %s", osTitle.c_str(),
    1030             :         // osLayerDataType.c_str());
    1031        7490 :         if (!osLayerDataType.empty() &&
    1032           0 :             (EQUAL(osLayerDataType.c_str(), "Raster") ||
    1033        7490 :              EQUAL(osLayerDataType.c_str(), "Coverage")) &&
    1034           0 :             (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
    1035             :         {
    1036           0 :             continue;
    1037             :         }
    1038        7490 :         if (!osLayerDataType.empty() &&
    1039        7490 :             EQUAL(osLayerDataType.c_str(), "Vector") &&
    1040           0 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    1041             :         {
    1042           0 :             continue;
    1043             :         }
    1044        7490 :         osURL.clear();
    1045       14980 :         const auto oLinks = oCollection.GetArray("links");
    1046       38649 :         for (const auto &oLink : oLinks)
    1047             :         {
    1048       69808 :             if (oLink["rel"].ToString() == "self" &&
    1049       38649 :                 oLink["type"].ToString() == "application/json")
    1050             :             {
    1051        6741 :                 osURL = BuildURL(oLink["href"].ToString());
    1052        6741 :                 break;
    1053             :             }
    1054       49585 :             else if (oLink["rel"].ToString() == "self" &&
    1055       25167 :                      oLink.GetString("type").empty())
    1056             :             {
    1057         749 :                 osURL = BuildURL(oLink["href"].ToString());
    1058             :             }
    1059             :         }
    1060        7490 :         if (osURL.empty())
    1061             :         {
    1062           0 :             continue;
    1063             :         }
    1064        7490 :         const int nIdx = 1 + aosSubdatasets.size() / 2;
    1065             :         aosSubdatasets.AddNameValue(CPLSPrintf("SUBDATASET_%d_NAME", nIdx),
    1066        7490 :                                     CPLSPrintf("OGCAPI:%s", osURL.c_str()));
    1067             :         aosSubdatasets.AddNameValue(
    1068             :             CPLSPrintf("SUBDATASET_%d_DESC", nIdx),
    1069        7490 :             CPLSPrintf("Collection %s", osTitle.c_str()));
    1070             :     }
    1071          10 :     SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
    1072             : 
    1073          10 :     return true;
    1074             : }
    1075             : 
    1076             : /************************************************************************/
    1077             : /*                           SelectImageURL()                           */
    1078             : /************************************************************************/
    1079             : 
    1080             : static const std::pair<std::string, std::string>
    1081          19 : SelectImageURL(const char *const *papszOptionOptions,
    1082             :                std::map<std::string, std::string> &oMapItemUrls)
    1083             : {
    1084             :     // Map IMAGE_FORMATS to their content types. Would be nice if this was
    1085             :     // globally defined someplace
    1086             :     const std::map<std::string, std::vector<std::string>>
    1087             :         oFormatContentTypeMap = {
    1088             :             {"AUTO",
    1089             :              {"image/png", "image/jpeg", "image/tiff; application=geotiff"}},
    1090             :             {"PNG_PREFERRED",
    1091             :              {"image/png", "image/jpeg", "image/tiff; application=geotiff"}},
    1092             :             {"JPEG_PREFERRED",
    1093             :              {"image/jpeg", "image/png", "image/tiff; application=geotiff"}},
    1094             :             {"PNG", {"image/png"}},
    1095             :             {"JPEG", {"image/jpeg"}},
    1096         532 :             {"GEOTIFF", {"image/tiff; application=geotiff"}}};
    1097             : 
    1098             :     // Get the IMAGE_FORMAT
    1099             :     const std::string osFormat =
    1100          38 :         CSLFetchNameValueDef(papszOptionOptions, "IMAGE_FORMAT", "AUTO");
    1101             : 
    1102             :     // Get a list of content types we will search for in priority order based on IMAGE_FORMAT
    1103          19 :     auto iterFormat = oFormatContentTypeMap.find(osFormat);
    1104          19 :     if (iterFormat == oFormatContentTypeMap.end())
    1105             :     {
    1106           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1107             :                  "Unknown IMAGE_FORMAT specified: %s", osFormat.c_str());
    1108           0 :         return std::pair<std::string, CPLString>();
    1109             :     }
    1110          38 :     std::vector<std::string> oContentTypes = iterFormat->second;
    1111             : 
    1112             :     // For "special" IMAGE_FORMATS we will also accept additional content types
    1113             :     // specified by the server. Note that this will likely result in having
    1114             :     // some content types duplicated in the vector but that is fine.
    1115          23 :     if (osFormat == "AUTO" || osFormat == "PNG_PREFERRED" ||
    1116           4 :         osFormat == "JPEG_PREFERRED")
    1117             :     {
    1118             :         std::transform(oMapItemUrls.begin(), oMapItemUrls.end(),
    1119             :                        std::back_inserter(oContentTypes),
    1120          44 :                        [](const auto &pair) -> const std::string &
    1121          60 :                        { return pair.first; });
    1122             :     }
    1123             : 
    1124             :     // Loop over each content type - return the first one we find
    1125          34 :     for (auto &oContentType : oContentTypes)
    1126             :     {
    1127          29 :         auto iterContentType = oMapItemUrls.find(oContentType);
    1128          29 :         if (iterContentType != oMapItemUrls.end())
    1129             :         {
    1130          14 :             return *iterContentType;
    1131             :         }
    1132             :     }
    1133             : 
    1134           5 :     if (osFormat != "AUTO")
    1135             :     {
    1136           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1137             :                  "Server does not support specified IMAGE_FORMAT: %s",
    1138             :                  osFormat.c_str());
    1139             :     }
    1140           5 :     return std::pair<std::string, CPLString>();
    1141             : }
    1142             : 
    1143             : /************************************************************************/
    1144             : /*                       SelectVectorFormatURL()                        */
    1145             : /************************************************************************/
    1146             : 
    1147             : static const CPLString
    1148          18 : SelectVectorFormatURL(const char *const *papszOptionOptions,
    1149             :                       const CPLString &osMVT_URL,
    1150             :                       const CPLString &osGEOJSON_URL)
    1151             : {
    1152             :     const char *pszFormat =
    1153          18 :         CSLFetchNameValueDef(papszOptionOptions, "VECTOR_FORMAT", "AUTO");
    1154          18 :     if (EQUAL(pszFormat, "AUTO") || EQUAL(pszFormat, "MVT_PREFERRED"))
    1155          12 :         return !osMVT_URL.empty() ? osMVT_URL : osGEOJSON_URL;
    1156           6 :     else if (EQUAL(pszFormat, "MVT"))
    1157           2 :         return osMVT_URL;
    1158           4 :     else if (EQUAL(pszFormat, "GEOJSON"))
    1159           2 :         return osGEOJSON_URL;
    1160           2 :     else if (EQUAL(pszFormat, "GEOJSON_PREFERRED"))
    1161           2 :         return !osGEOJSON_URL.empty() ? osGEOJSON_URL : osMVT_URL;
    1162           0 :     return CPLString();
    1163             : }
    1164             : 
    1165             : /************************************************************************/
    1166             : /*                           InitWithMapAPI()                           */
    1167             : /************************************************************************/
    1168             : 
    1169           1 : bool OGCAPIDataset::InitWithMapAPI(GDALOpenInfo *poOpenInfo,
    1170             :                                    const CPLJSONObject &oRoot, double dfXMin,
    1171             :                                    double dfYMin, double dfXMax, double dfYMax)
    1172             : {
    1173           3 :     auto oLinks = oRoot["links"].ToArray();
    1174             : 
    1175             :     // Key - mime type, Value url
    1176           2 :     std::map<std::string, std::string> oMapItemUrls;
    1177             : 
    1178          36 :     for (const auto &oLink : oLinks)
    1179             :     {
    1180          70 :         if (oLink["rel"].ToString() ==
    1181          73 :                 "http://www.opengis.net/def/rel/ogc/1.0/map" &&
    1182          38 :             oLink["type"].IsValid())
    1183             :         {
    1184           6 :             oMapItemUrls[oLink["type"].ToString()] =
    1185           9 :                 BuildURL(oLink["href"].ToString());
    1186             :         }
    1187             :         else
    1188             :         {
    1189             :             // For lack of additional information assume we are getting some bytes
    1190          64 :             oMapItemUrls["application/octet-stream"] =
    1191          96 :                 BuildURL(oLink["href"].ToString());
    1192             :         }
    1193             :     }
    1194             : 
    1195             :     const std::pair<std::string, std::string> oContentUrlPair =
    1196           2 :         SelectImageURL(poOpenInfo->papszOpenOptions, oMapItemUrls);
    1197           2 :     const std::string osContentType = oContentUrlPair.first;
    1198           2 :     const std::string osImageURL = oContentUrlPair.second;
    1199             : 
    1200           1 :     if (osImageURL.empty())
    1201             :     {
    1202           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1203             :                  "Cannot find link to tileset items");
    1204           0 :         return false;
    1205             :     }
    1206             : 
    1207           1 :     int l_nBands = FigureBands(osContentType, osImageURL);
    1208           1 :     int nOverviewCount = 0;
    1209           1 :     int nLargestDim = std::max(nRasterXSize, nRasterYSize);
    1210          24 :     while (nLargestDim > 256)
    1211             :     {
    1212          23 :         nOverviewCount++;
    1213          23 :         nLargestDim /= 2;
    1214             :     }
    1215             : 
    1216           1 :     m_oSRS.importFromEPSG(4326);
    1217           1 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1218             : 
    1219           1 :     const bool bCache = CPLTestBool(
    1220           1 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
    1221           1 :     const int nMaxConnections = atoi(
    1222           1 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
    1223             :                              CPLGetConfigOption("GDAL_MAX_CONNECTIONS", "5")));
    1224           2 :     CPLString osWMS_XML;
    1225           1 :     char *pszEscapedURL = CPLEscapeString(osImageURL.c_str(), -1, CPLES_XML);
    1226             :     osWMS_XML.Printf("<GDAL_WMS>"
    1227             :                      "    <Service name=\"OGCAPIMaps\">"
    1228             :                      "        <ServerUrl>%s</ServerUrl>"
    1229             :                      "    </Service>"
    1230             :                      "    <DataWindow>"
    1231             :                      "        <UpperLeftX>%.17g</UpperLeftX>"
    1232             :                      "        <UpperLeftY>%.17g</UpperLeftY>"
    1233             :                      "        <LowerRightX>%.17g</LowerRightX>"
    1234             :                      "        <LowerRightY>%.17g</LowerRightY>"
    1235             :                      "        <SizeX>%d</SizeX>"
    1236             :                      "        <SizeY>%d</SizeY>"
    1237             :                      "    </DataWindow>"
    1238             :                      "    <OverviewCount>%d</OverviewCount>"
    1239             :                      "    <BlockSizeX>256</BlockSizeX>"
    1240             :                      "    <BlockSizeY>256</BlockSizeY>"
    1241             :                      "    <BandsCount>%d</BandsCount>"
    1242             :                      "    <MaxConnections>%d</MaxConnections>"
    1243             :                      "    %s"
    1244             :                      "</GDAL_WMS>",
    1245             :                      pszEscapedURL, dfXMin, dfYMax, dfXMax, dfYMin,
    1246             :                      nRasterXSize, nRasterYSize, nOverviewCount, l_nBands,
    1247           1 :                      nMaxConnections, bCache ? "<Cache />" : "");
    1248           1 :     CPLFree(pszEscapedURL);
    1249           1 :     CPLDebug("OGCAPI", "%s", osWMS_XML.c_str());
    1250           1 :     m_poWMSDS.reset(
    1251             :         GDALDataset::Open(osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
    1252           1 :     if (m_poWMSDS == nullptr)
    1253           0 :         return false;
    1254             : 
    1255           5 :     for (int i = 1; i <= m_poWMSDS->GetRasterCount(); i++)
    1256             :     {
    1257           4 :         SetBand(i, new OGCAPIMapWrapperBand(this, i));
    1258             :     }
    1259           1 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1260             : 
    1261           1 :     return true;
    1262             : }
    1263             : 
    1264             : /************************************************************************/
    1265             : /*                        InitWithCoverageAPI()                         */
    1266             : /************************************************************************/
    1267             : 
    1268           1 : bool OGCAPIDataset::InitWithCoverageAPI(GDALOpenInfo *poOpenInfo,
    1269             :                                         const CPLString &osCoverageURL,
    1270             :                                         double dfXMin, double dfYMin,
    1271             :                                         double dfXMax, double dfYMax,
    1272             :                                         const CPLJSONObject &oJsonCollection)
    1273             : {
    1274           1 :     int l_nBands = 1;
    1275           1 :     GDALDataType eDT = GDT_Float32;
    1276             : 
    1277           3 :     auto oRangeType = oJsonCollection["rangeType"];
    1278           1 :     if (!oRangeType.IsValid())
    1279           1 :         oRangeType = oJsonCollection["rangetype"];
    1280             : 
    1281           3 :     auto oDomainSet = oJsonCollection["domainset"];
    1282           1 :     if (!oDomainSet.IsValid())
    1283           1 :         oDomainSet = oJsonCollection["domainSet"];
    1284             : 
    1285           1 :     if (!oRangeType.IsValid() || !oDomainSet.IsValid())
    1286             :     {
    1287           3 :         auto oLinks = oJsonCollection.GetArray("links");
    1288          28 :         for (const auto &oLink : oLinks)
    1289             :         {
    1290          81 :             const auto osRel = oLink.GetString("rel");
    1291          81 :             const auto osType = oLink.GetString("type");
    1292          27 :             if (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
    1293          28 :                          "coverage-domainset" &&
    1294           1 :                 (osType == "application/json" || osType.empty()))
    1295             :             {
    1296           3 :                 CPLString osURL = BuildURL(oLink["href"].ToString());
    1297           2 :                 CPLJSONDocument oDoc;
    1298           1 :                 if (DownloadJSon(osURL.c_str(), oDoc))
    1299             :                 {
    1300           1 :                     oDomainSet = oDoc.GetRoot();
    1301             :                 }
    1302             :             }
    1303          26 :             else if (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
    1304          27 :                               "coverage-rangetype" &&
    1305           1 :                      (osType == "application/json" || osType.empty()))
    1306             :             {
    1307           3 :                 CPLString osURL = BuildURL(oLink["href"].ToString());
    1308           2 :                 CPLJSONDocument oDoc;
    1309           1 :                 if (DownloadJSon(osURL.c_str(), oDoc))
    1310             :                 {
    1311           1 :                     oRangeType = oDoc.GetRoot();
    1312             :                 }
    1313             :             }
    1314             :         }
    1315             :     }
    1316             : 
    1317           1 :     if (oRangeType.IsValid())
    1318             :     {
    1319           3 :         auto oField = oRangeType.GetArray("field");
    1320           1 :         if (oField.IsValid())
    1321             :         {
    1322           1 :             l_nBands = oField.Size();
    1323             :             // Such as in https://maps.gnosis.earth/ogcapi/collections/NaturalEarth:raster:HYP_HR_SR_OB_DR/coverage/rangetype?f=json
    1324             :             // https://github.com/opengeospatial/coverage-implementation-schema/blob/main/standard/schemas/1.1/json/examples/generalGrid/2D_regular.json
    1325             :             std::string osDataType =
    1326           3 :                 oField[0].GetString("encodingInfo/dataType");
    1327           1 :             if (osDataType.empty())
    1328             :             {
    1329             :                 // Older way?
    1330           0 :                 osDataType = oField[0].GetString("definition");
    1331             :             }
    1332             :             static const std::map<std::string, GDALDataType> oMapTypes = {
    1333             :                 // https://edc-oapi.dev.hub.eox.at/oapi/collections/S2L2A
    1334           0 :                 {"UINT8", GDT_UInt8},
    1335           0 :                 {"INT16", GDT_Int16},
    1336           0 :                 {"UINT16", GDT_UInt16},
    1337           0 :                 {"INT32", GDT_Int32},
    1338           0 :                 {"UINT32", GDT_UInt32},
    1339           0 :                 {"FLOAT32", GDT_Float32},
    1340           0 :                 {"FLOAT64", GDT_Float64},
    1341             :                 // https://test.cubewerx.com/cubewerx/cubeserv/demo/ogcapi/Daraa/collections/Daraa_DTED/coverage/rangetype?f=json
    1342           0 :                 {"ogcType:unsignedByte", GDT_UInt8},
    1343           0 :                 {"ogcType:signedShort", GDT_Int16},
    1344           0 :                 {"ogcType:unsignedShort", GDT_UInt16},
    1345           0 :                 {"ogcType:signedInt", GDT_Int32},
    1346           0 :                 {"ogcType:unsignedInt", GDT_UInt32},
    1347           0 :                 {"ogcType:float32", GDT_Float32},
    1348           0 :                 {"ogcType:float64", GDT_Float64},
    1349           0 :                 {"ogcType:double", GDT_Float64},
    1350          16 :             };
    1351             :             // 08-094r1_SWE_Common_Data_Model_2.0_Submission_Package.pdf page
    1352             :             // 112
    1353             :             auto oIter = oMapTypes.find(
    1354           1 :                 CPLString(osDataType)
    1355             :                     .replaceAll("http://www.opengis.net/def/dataType/OGC/0/",
    1356           1 :                                 "ogcType:"));
    1357           1 :             if (oIter != oMapTypes.end())
    1358             :             {
    1359           1 :                 eDT = oIter->second;
    1360             :             }
    1361             :             else
    1362             :             {
    1363           0 :                 CPLDebug("OGCAPI", "Unhandled data type: %s",
    1364             :                          osDataType.c_str());
    1365             :             }
    1366             :         }
    1367             :     }
    1368             : 
    1369           2 :     CPLString osXAxisName;
    1370           2 :     CPLString osYAxisName;
    1371           1 :     if (oDomainSet.IsValid())
    1372             :     {
    1373           3 :         auto oAxisLabels = oDomainSet["generalGrid"]["axisLabels"].ToArray();
    1374           1 :         if (oAxisLabels.IsValid() && oAxisLabels.Size() >= 2)
    1375             :         {
    1376           1 :             osXAxisName = oAxisLabels[0].ToString();
    1377           1 :             osYAxisName = oAxisLabels[1].ToString();
    1378             :         }
    1379             : 
    1380           3 :         auto oAxis = oDomainSet["generalGrid"]["axis"].ToArray();
    1381           1 :         if (oAxis.IsValid() && oAxis.Size() >= 2)
    1382             :         {
    1383           1 :             double dfXRes = std::abs(oAxis[0].GetDouble("resolution"));
    1384           1 :             double dfYRes = std::abs(oAxis[1].GetDouble("resolution"));
    1385             : 
    1386           1 :             dfXMin = oAxis[0].GetDouble("lowerBound");
    1387           1 :             dfXMax = oAxis[0].GetDouble("upperBound");
    1388           1 :             dfYMin = oAxis[1].GetDouble("lowerBound");
    1389           1 :             dfYMax = oAxis[1].GetDouble("upperBound");
    1390             : 
    1391           1 :             if (osXAxisName == "Lat")
    1392             :             {
    1393           1 :                 std::swap(dfXRes, dfYRes);
    1394           1 :                 std::swap(dfXMin, dfYMin);
    1395           1 :                 std::swap(dfXMax, dfYMax);
    1396             :             }
    1397             : 
    1398           1 :             double dfXSize = (dfXMax - dfXMin) / dfXRes;
    1399           1 :             double dfYSize = (dfYMax - dfYMin) / dfYRes;
    1400           1 :             while (dfXSize > INT_MAX || dfYSize > INT_MAX)
    1401             :             {
    1402           0 :                 dfXSize /= 2;
    1403           0 :                 dfYSize /= 2;
    1404             :             }
    1405             : 
    1406           1 :             nRasterXSize = std::max(1, static_cast<int>(0.5 + dfXSize));
    1407           1 :             nRasterYSize = std::max(1, static_cast<int>(0.5 + dfYSize));
    1408           1 :             m_gt.xorig = dfXMin;
    1409           1 :             m_gt.xscale = (dfXMax - dfXMin) / nRasterXSize;
    1410           1 :             m_gt.yorig = dfYMax;
    1411           1 :             m_gt.yscale = -(dfYMax - dfYMin) / nRasterYSize;
    1412             :         }
    1413             : 
    1414           2 :         OGRSpatialReference oSRS;
    1415           3 :         std::string srsName(oDomainSet["generalGrid"].GetString("srsName"));
    1416           1 :         bool bSwap = false;
    1417             : 
    1418             :         // Strip of time component, as found in
    1419             :         // OGCAPI:https://maps.ecere.com/ogcapi/collections/blueMarble
    1420           1 :         if (STARTS_WITH(srsName.c_str(),
    1421           1 :                         "http://www.opengis.net/def/crs-compound?1=") &&
    1422           0 :             srsName.find("&2=http://www.opengis.net/def/crs/OGC/0/") !=
    1423             :                 std::string::npos)
    1424             :         {
    1425           0 :             srsName = srsName.substr(
    1426           0 :                 strlen("http://www.opengis.net/def/crs-compound?1="));
    1427           0 :             srsName.resize(srsName.find("&2="));
    1428             :         }
    1429             : 
    1430           1 :         if (oSRS.SetFromUserInput(
    1431             :                 srsName.c_str(),
    1432           1 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1433             :             OGRERR_NONE)
    1434             :         {
    1435           1 :             if (oSRS.EPSGTreatsAsLatLong() ||
    1436           0 :                 oSRS.EPSGTreatsAsNorthingEasting())
    1437             :             {
    1438           1 :                 bSwap = true;
    1439             :             }
    1440             :         }
    1441           0 :         else if (srsName ==
    1442             :                  "https://ows.rasdaman.org/def/crs/EPSG/0/4326")  // HACK
    1443             :         {
    1444           0 :             bSwap = true;
    1445             :         }
    1446           1 :         if (bSwap)
    1447             :         {
    1448           1 :             std::swap(osXAxisName, osYAxisName);
    1449             :         }
    1450             :     }
    1451             : 
    1452           1 :     int nOverviewCount = 0;
    1453           1 :     int nLargestDim = std::max(nRasterXSize, nRasterYSize);
    1454          12 :     while (nLargestDim > 256)
    1455             :     {
    1456          11 :         nOverviewCount++;
    1457          11 :         nLargestDim /= 2;
    1458             :     }
    1459             : 
    1460           1 :     m_oSRS.importFromEPSG(4326);
    1461           1 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1462             : 
    1463           2 :     CPLString osCoverageURLModified(osCoverageURL);
    1464           2 :     if (osCoverageURLModified.find('&') == std::string::npos &&
    1465           1 :         osCoverageURLModified.find('?') == std::string::npos)
    1466             :     {
    1467           0 :         osCoverageURLModified += '?';
    1468             :     }
    1469             :     else
    1470             :     {
    1471           1 :         osCoverageURLModified += '&';
    1472             :     }
    1473             : 
    1474           1 :     if (!osXAxisName.empty() && !osYAxisName.empty())
    1475             :     {
    1476             :         osCoverageURLModified +=
    1477             :             CPLSPrintf("subset=%s(${minx}:${maxx}),%s(${miny}:${maxy})&"
    1478             :                        "scaleSize=%s(${width}),%s(${height})",
    1479             :                        osXAxisName.c_str(), osYAxisName.c_str(),
    1480           1 :                        osXAxisName.c_str(), osYAxisName.c_str());
    1481             :     }
    1482             :     else
    1483             :     {
    1484             :         // FIXME
    1485             :         osCoverageURLModified += "bbox=${minx},${miny},${maxx},${maxy}&"
    1486           0 :                                  "scaleSize=Lat(${height}),Long(${width})";
    1487             :     }
    1488             : 
    1489           1 :     const bool bCache = CPLTestBool(
    1490           1 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
    1491           1 :     const int nMaxConnections = atoi(
    1492           1 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
    1493             :                              CPLGetConfigOption("GDAL_MAX_CONNECTIONS", "5")));
    1494           2 :     CPLString osWMS_XML;
    1495           1 :     char *pszEscapedURL = CPLEscapeString(osCoverageURLModified, -1, CPLES_XML);
    1496           2 :     std::string osAccept("<Accept>image/tiff;application=geotiff</Accept>");
    1497           1 :     osWMS_XML.Printf("<GDAL_WMS>"
    1498             :                      "    <Service name=\"OGCAPICoverage\">"
    1499             :                      "        <ServerUrl>%s</ServerUrl>"
    1500             :                      "    </Service>"
    1501             :                      "    <DataWindow>"
    1502             :                      "        <UpperLeftX>%.17g</UpperLeftX>"
    1503             :                      "        <UpperLeftY>%.17g</UpperLeftY>"
    1504             :                      "        <LowerRightX>%.17g</LowerRightX>"
    1505             :                      "        <LowerRightY>%.17g</LowerRightY>"
    1506             :                      "        <SizeX>%d</SizeX>"
    1507             :                      "        <SizeY>%d</SizeY>"
    1508             :                      "    </DataWindow>"
    1509             :                      "    <OverviewCount>%d</OverviewCount>"
    1510             :                      "    <BlockSizeX>256</BlockSizeX>"
    1511             :                      "    <BlockSizeY>256</BlockSizeY>"
    1512             :                      "    <BandsCount>%d</BandsCount>"
    1513             :                      "    <DataType>%s</DataType>"
    1514             :                      "    <MaxConnections>%d</MaxConnections>"
    1515             :                      "    %s"
    1516             :                      "    %s"
    1517             :                      "</GDAL_WMS>",
    1518             :                      pszEscapedURL, dfXMin, dfYMax, dfXMax, dfYMin,
    1519             :                      nRasterXSize, nRasterYSize, nOverviewCount, l_nBands,
    1520             :                      GDALGetDataTypeName(eDT), nMaxConnections,
    1521           1 :                      osAccept.c_str(), bCache ? "<Cache />" : "");
    1522           1 :     CPLFree(pszEscapedURL);
    1523           1 :     CPLDebug("OGCAPI", "%s", osWMS_XML.c_str());
    1524           1 :     m_poWMSDS.reset(
    1525             :         GDALDataset::Open(osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
    1526           1 :     if (m_poWMSDS == nullptr)
    1527           0 :         return false;
    1528             : 
    1529           2 :     for (int i = 1; i <= m_poWMSDS->GetRasterCount(); i++)
    1530             :     {
    1531           1 :         SetBand(i, new OGCAPIMapWrapperBand(this, i));
    1532             :     }
    1533           1 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1534             : 
    1535           1 :     return true;
    1536             : }
    1537             : 
    1538             : /************************************************************************/
    1539             : /*                        OGCAPIMapWrapperBand()                        */
    1540             : /************************************************************************/
    1541             : 
    1542           5 : OGCAPIMapWrapperBand::OGCAPIMapWrapperBand(OGCAPIDataset *poDSIn, int nBandIn)
    1543             : {
    1544           5 :     poDS = poDSIn;
    1545           5 :     nBand = nBandIn;
    1546           5 :     eDataType = poDSIn->m_poWMSDS->GetRasterBand(nBand)->GetRasterDataType();
    1547           5 :     poDSIn->m_poWMSDS->GetRasterBand(nBand)->GetBlockSize(&nBlockXSize,
    1548             :                                                           &nBlockYSize);
    1549           5 : }
    1550             : 
    1551             : /************************************************************************/
    1552             : /*                             IReadBlock()                             */
    1553             : /************************************************************************/
    1554             : 
    1555           0 : CPLErr OGCAPIMapWrapperBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    1556             :                                         void *pImage)
    1557             : {
    1558           0 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    1559           0 :     return poGDS->m_poWMSDS->GetRasterBand(nBand)->ReadBlock(
    1560           0 :         nBlockXOff, nBlockYOff, pImage);
    1561             : }
    1562             : 
    1563             : /************************************************************************/
    1564             : /*                             IRasterIO()                              */
    1565             : /************************************************************************/
    1566             : 
    1567           1 : CPLErr OGCAPIMapWrapperBand::IRasterIO(
    1568             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1569             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1570             :     GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
    1571             : {
    1572           1 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    1573           1 :     return poGDS->m_poWMSDS->GetRasterBand(nBand)->RasterIO(
    1574             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1575           1 :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
    1576             : }
    1577             : 
    1578             : /************************************************************************/
    1579             : /*                          GetOverviewCount()                          */
    1580             : /************************************************************************/
    1581             : 
    1582           4 : int OGCAPIMapWrapperBand::GetOverviewCount()
    1583             : {
    1584           4 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    1585           4 :     return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetOverviewCount();
    1586             : }
    1587             : 
    1588             : /************************************************************************/
    1589             : /*                            GetOverview()                             */
    1590             : /************************************************************************/
    1591             : 
    1592           0 : GDALRasterBand *OGCAPIMapWrapperBand::GetOverview(int nLevel)
    1593             : {
    1594           0 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    1595           0 :     return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetOverview(nLevel);
    1596             : }
    1597             : 
    1598             : /************************************************************************/
    1599             : /*                       GetColorInterpretation()                       */
    1600             : /************************************************************************/
    1601             : 
    1602           6 : GDALColorInterp OGCAPIMapWrapperBand::GetColorInterpretation()
    1603             : {
    1604           6 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    1605             :     // The WMS driver returns Grey-Alpha for 2 band, RGB(A) for 3 or 4 bands
    1606             :     // Restrict that behavior to Byte only data.
    1607           6 :     if (eDataType == GDT_UInt8)
    1608           5 :         return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetColorInterpretation();
    1609           1 :     return GCI_Undefined;
    1610             : }
    1611             : 
    1612             : /************************************************************************/
    1613             : /*                           ParseXMLSchema()                           */
    1614             : /************************************************************************/
    1615             : 
    1616             : static bool
    1617           0 : ParseXMLSchema(const std::string &osURL,
    1618             :                std::vector<std::unique_ptr<OGRFieldDefn>> &apoFields,
    1619             :                OGRwkbGeometryType &eGeomType)
    1620             : {
    1621           0 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1622             : 
    1623           0 :     std::vector<GMLFeatureClass *> apoClasses;
    1624           0 :     bool bFullyUnderstood = false;
    1625           0 :     bool bUseSchemaImports = false;
    1626           0 :     bool bHaveSchema = GMLParseXSD(osURL.c_str(), bUseSchemaImports, apoClasses,
    1627             :                                    bFullyUnderstood);
    1628           0 :     if (bHaveSchema && apoClasses.size() == 1)
    1629             :     {
    1630           0 :         auto poGMLFeatureClass = apoClasses[0];
    1631           0 :         if (poGMLFeatureClass->GetGeometryPropertyCount() == 1 &&
    1632           0 :             poGMLFeatureClass->GetGeometryProperty(0)->GetType() != wkbUnknown)
    1633             :         {
    1634           0 :             eGeomType = static_cast<OGRwkbGeometryType>(
    1635           0 :                 poGMLFeatureClass->GetGeometryProperty(0)->GetType());
    1636             :         }
    1637             : 
    1638           0 :         const int nPropertyCount = poGMLFeatureClass->GetPropertyCount();
    1639           0 :         for (int iField = 0; iField < nPropertyCount; iField++)
    1640             :         {
    1641           0 :             const auto poProperty = poGMLFeatureClass->GetProperty(iField);
    1642           0 :             OGRFieldSubType eSubType = OFSTNone;
    1643             :             const OGRFieldType eFType =
    1644           0 :                 GML_GetOGRFieldType(poProperty->GetType(), eSubType);
    1645             : 
    1646           0 :             const char *pszName = poProperty->GetName();
    1647           0 :             auto poField = std::make_unique<OGRFieldDefn>(pszName, eFType);
    1648           0 :             poField->SetSubType(eSubType);
    1649           0 :             apoFields.emplace_back(std::move(poField));
    1650             :         }
    1651           0 :         delete poGMLFeatureClass;
    1652           0 :         return true;
    1653             :     }
    1654             : 
    1655           0 :     for (auto poFeatureClass : apoClasses)
    1656           0 :         delete poFeatureClass;
    1657             : 
    1658           0 :     return false;
    1659             : }
    1660             : 
    1661             : /************************************************************************/
    1662             : /*                          InitWithTilesAPI()                          */
    1663             : /************************************************************************/
    1664             : 
    1665          18 : bool OGCAPIDataset::InitWithTilesAPI(GDALOpenInfo *poOpenInfo,
    1666             :                                      const CPLString &osTilesURL, bool bIsMap,
    1667             :                                      double dfXMin, double dfYMin,
    1668             :                                      double dfXMax, double dfYMax,
    1669             :                                      bool bBBOXIsInCRS84,
    1670             :                                      const CPLJSONObject &oJsonCollection)
    1671             : {
    1672          36 :     CPLJSONDocument oDoc;
    1673          18 :     if (!DownloadJSon(osTilesURL.c_str(), oDoc))
    1674           0 :         return false;
    1675             : 
    1676          54 :     auto oTilesets = oDoc.GetRoot()["tilesets"].ToArray();
    1677          18 :     if (oTilesets.Size() == 0)
    1678             :     {
    1679           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tilesets");
    1680           0 :         return false;
    1681             :     }
    1682             :     const char *pszRequiredTileMatrixSet =
    1683          18 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILEMATRIXSET");
    1684          36 :     const char *pszPreferredTileMatrixSet = CSLFetchNameValue(
    1685          18 :         poOpenInfo->papszOpenOptions, "PREFERRED_TILEMATRIXSET");
    1686          36 :     CPLString osTilesetURL;
    1687         180 :     for (const auto &oTileset : oTilesets)
    1688             :     {
    1689         324 :         const auto oTileMatrixSetURI = oTileset.GetString("tileMatrixSetURI");
    1690         324 :         const auto oLinks = oTileset.GetArray("links");
    1691         162 :         if (bIsMap)
    1692             :         {
    1693         117 :             if (oTileset.GetString("dataType") != "map")
    1694           0 :                 continue;
    1695             :         }
    1696             :         else
    1697             :         {
    1698          45 :             if (oTileset.GetString("dataType") != "vector")
    1699           0 :                 continue;
    1700             :         }
    1701         162 :         if (!oLinks.IsValid())
    1702             :         {
    1703           0 :             CPLDebug("OGCAPI", "Missing links for a tileset");
    1704           0 :             continue;
    1705             :         }
    1706         225 :         if (pszRequiredTileMatrixSet != nullptr &&
    1707          63 :             oTileMatrixSetURI.find(pszRequiredTileMatrixSet) ==
    1708             :                 std::string::npos)
    1709             :         {
    1710          56 :             continue;
    1711             :         }
    1712         212 :         CPLString osCandidateTilesetURL;
    1713         318 :         for (const auto &oLink : oLinks)
    1714             :         {
    1715         212 :             if (oLink["rel"].ToString() == "self")
    1716             :             {
    1717         212 :                 const auto osType = oLink["type"].ToString();
    1718         106 :                 if (osType == MEDIA_TYPE_JSON)
    1719             :                 {
    1720         106 :                     osCandidateTilesetURL = BuildURL(oLink["href"].ToString());
    1721         106 :                     break;
    1722             :                 }
    1723           0 :                 else if (osType.empty())
    1724             :                 {
    1725           0 :                     osCandidateTilesetURL = BuildURL(oLink["href"].ToString());
    1726             :                 }
    1727             :             }
    1728             :         }
    1729         106 :         if (pszRequiredTileMatrixSet != nullptr)
    1730             :         {
    1731           7 :             osTilesetURL = std::move(osCandidateTilesetURL);
    1732             :         }
    1733          99 :         else if (pszPreferredTileMatrixSet != nullptr &&
    1734          99 :                  !osCandidateTilesetURL.empty() &&
    1735           0 :                  (oTileMatrixSetURI.find(pszPreferredTileMatrixSet) !=
    1736             :                   std::string::npos))
    1737             :         {
    1738           0 :             osTilesetURL = std::move(osCandidateTilesetURL);
    1739             :         }
    1740          99 :         else if (oTileMatrixSetURI.find("WorldCRS84Quad") != std::string::npos)
    1741             :         {
    1742          11 :             osTilesetURL = std::move(osCandidateTilesetURL);
    1743             :         }
    1744          88 :         else if (osTilesetURL.empty())
    1745             :         {
    1746          11 :             osTilesetURL = std::move(osCandidateTilesetURL);
    1747             :         }
    1748             :     }
    1749          18 :     if (osTilesetURL.empty())
    1750             :     {
    1751           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tilematrixset");
    1752           0 :         return false;
    1753             :     }
    1754             : 
    1755             :     // Download and parse selected tileset definition
    1756          18 :     if (!DownloadJSon(osTilesetURL.c_str(), oDoc))
    1757           0 :         return false;
    1758             : 
    1759          54 :     const auto oLinks = oDoc.GetRoot().GetArray("links");
    1760          18 :     if (!oLinks.IsValid())
    1761             :     {
    1762           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing links for tileset");
    1763           0 :         return false;
    1764             :     }
    1765             : 
    1766             :     // Key - mime type, Value url
    1767          36 :     std::map<std::string, std::string> oMapItemUrls;
    1768          36 :     CPLString osMVT_URL;
    1769          36 :     CPLString osGEOJSON_URL;
    1770          36 :     CPLString osTilingSchemeURL;
    1771          18 :     bool bTilingSchemeURLJson = false;
    1772             : 
    1773         203 :     for (const auto &oLink : oLinks)
    1774             :     {
    1775         555 :         const auto osRel = oLink.GetString("rel");
    1776         555 :         const auto osType = oLink.GetString("type");
    1777             : 
    1778         275 :         if (!bTilingSchemeURLJson &&
    1779          90 :             osRel == "http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme")
    1780             :         {
    1781          18 :             if (osType == MEDIA_TYPE_JSON)
    1782             :             {
    1783          18 :                 bTilingSchemeURLJson = true;
    1784          18 :                 osTilingSchemeURL = BuildURL(oLink["href"].ToString());
    1785             :             }
    1786           0 :             else if (osType.empty())
    1787             :             {
    1788           0 :                 osTilingSchemeURL = BuildURL(oLink["href"].ToString());
    1789             :             }
    1790             :         }
    1791         167 :         else if (bIsMap)
    1792             :         {
    1793         117 :             if (osRel == "item" && !osType.empty())
    1794             :             {
    1795          52 :                 oMapItemUrls[osType] = BuildURL(oLink["href"].ToString());
    1796             :             }
    1797          65 :             else if (osRel == "item")
    1798             :             {
    1799             :                 // For lack of additional information assume we are getting some bytes
    1800           0 :                 oMapItemUrls["application/octet-stream"] =
    1801           0 :                     BuildURL(oLink["href"].ToString());
    1802             :             }
    1803             :         }
    1804             :         else
    1805             :         {
    1806          75 :             if (osRel == "item" &&
    1807          25 :                 osType == "application/vnd.mapbox-vector-tile")
    1808             :             {
    1809           5 :                 osMVT_URL = BuildURL(oLink["href"].ToString());
    1810             :             }
    1811          45 :             else if (osRel == "item" && osType == "application/geo+json")
    1812             :             {
    1813           5 :                 osGEOJSON_URL = BuildURL(oLink["href"].ToString());
    1814             :             }
    1815             :         }
    1816             :     }
    1817             : 
    1818          18 :     if (osTilingSchemeURL.empty())
    1819             :     {
    1820           0 :         CPLError(
    1821             :             CE_Failure, CPLE_AppDefined,
    1822             :             "Cannot find http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme");
    1823           0 :         return false;
    1824             :     }
    1825             : 
    1826             :     // Parse tile matrix set limits.
    1827             :     const auto oTileMatrixSetLimits =
    1828          54 :         oDoc.GetRoot().GetArray("tileMatrixSetLimits");
    1829             : 
    1830             :     struct Limits
    1831             :     {
    1832             :         int minTileRow;
    1833             :         int maxTileRow;
    1834             :         int minTileCol;
    1835             :         int maxTileCol;
    1836             :     };
    1837             : 
    1838          36 :     std::map<CPLString, Limits> oMapTileMatrixSetLimits;
    1839          18 :     if (CPLTestBool(
    1840             :             CPLGetConfigOption("GDAL_OGCAPI_TILEMATRIXSET_LIMITS", "YES")))
    1841             :     {
    1842         172 :         for (const auto &jsonLimit : oTileMatrixSetLimits)
    1843             :         {
    1844         308 :             const auto osTileMatrix = jsonLimit.GetString("tileMatrix");
    1845         154 :             if (!osTileMatrix.empty())
    1846             :             {
    1847             :                 Limits limits;
    1848         154 :                 limits.minTileRow = jsonLimit.GetInteger("minTileRow");
    1849         154 :                 limits.maxTileRow = jsonLimit.GetInteger("maxTileRow");
    1850         154 :                 limits.minTileCol = jsonLimit.GetInteger("minTileCol");
    1851         154 :                 limits.maxTileCol = jsonLimit.GetInteger("maxTileCol");
    1852         154 :                 if (limits.minTileRow > limits.maxTileRow)
    1853           0 :                     continue;  // shouldn't happen on valid data
    1854         154 :                 oMapTileMatrixSetLimits[osTileMatrix] = limits;
    1855             :             }
    1856             :         }
    1857             :     }
    1858             : 
    1859             :     const std::pair<std::string, std::string> oContentUrlPair =
    1860          36 :         SelectImageURL(poOpenInfo->papszOpenOptions, oMapItemUrls);
    1861          36 :     const std::string osContentType = oContentUrlPair.first;
    1862          36 :     const std::string osRasterURL = oContentUrlPair.second;
    1863             : 
    1864             :     const CPLString osVectorURL = SelectVectorFormatURL(
    1865          36 :         poOpenInfo->papszOpenOptions, osMVT_URL, osGEOJSON_URL);
    1866          18 :     if (osRasterURL.empty() && osVectorURL.empty())
    1867             :     {
    1868           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1869             :                  "Cannot find link to PNG, JPEG, MVT or GeoJSON tiles");
    1870           0 :         return false;
    1871             :     }
    1872             : 
    1873          72 :     for (const char *pszNeedle : {"{tileMatrix}", "{tileRow}", "{tileCol}"})
    1874             :     {
    1875          93 :         if (!osRasterURL.empty() &&
    1876          39 :             osRasterURL.find(pszNeedle) == std::string::npos)
    1877             :         {
    1878           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s missing in tile URL %s",
    1879             :                      pszNeedle, osRasterURL.c_str());
    1880           0 :             return false;
    1881             :         }
    1882          69 :         if (!osVectorURL.empty() &&
    1883          15 :             osVectorURL.find(pszNeedle) == std::string::npos)
    1884             :         {
    1885           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s missing in tile URL %s",
    1886             :                      pszNeedle, osVectorURL.c_str());
    1887           0 :             return false;
    1888             :         }
    1889             :     }
    1890             : 
    1891             :     // Download and parse tile matrix set definition
    1892          18 :     if (!DownloadJSon(osTilingSchemeURL.c_str(), oDoc, nullptr,
    1893             :                       MEDIA_TYPE_JSON))
    1894           0 :         return false;
    1895             : 
    1896          36 :     auto tms = gdal::TileMatrixSet::parse(oDoc.SaveAsString().c_str());
    1897          18 :     if (tms == nullptr)
    1898           0 :         return false;
    1899             : 
    1900          36 :     if (m_oSRS.SetFromUserInput(
    1901          18 :             tms->crs().c_str(),
    1902          18 :             OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    1903             :         OGRERR_NONE)
    1904           0 :         return false;
    1905          36 :     const bool bInvertAxis = m_oSRS.EPSGTreatsAsLatLong() != FALSE ||
    1906          18 :                              m_oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
    1907          18 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1908             : 
    1909          18 :     bool bFoundSomething = false;
    1910          18 :     if (!osVectorURL.empty() && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
    1911             :     {
    1912          15 :         const auto osVectorType = oJsonCollection.GetString("vectorType");
    1913           5 :         OGRwkbGeometryType eGeomType = wkbUnknown;
    1914           5 :         if (osVectorType == "Points")
    1915           0 :             eGeomType = wkbPoint;
    1916           5 :         else if (osVectorType == "Lines")
    1917           0 :             eGeomType = wkbMultiLineString;
    1918           5 :         else if (osVectorType == "Polygons")
    1919           0 :             eGeomType = wkbMultiPolygon;
    1920             : 
    1921          10 :         CPLString osXMLSchemaURL;
    1922         180 :         for (const auto &oLink : oJsonCollection.GetArray("links"))
    1923             :         {
    1924         350 :             if (oLink["rel"].ToString() == "describedBy" &&
    1925         175 :                 oLink["type"].ToString() == "text/xml")
    1926             :             {
    1927           0 :                 osXMLSchemaURL = BuildURL(oLink["href"].ToString());
    1928             :             }
    1929             :         }
    1930             : 
    1931           5 :         std::vector<std::unique_ptr<OGRFieldDefn>> apoFields;
    1932           5 :         bool bGotSchema = false;
    1933           5 :         if (!osXMLSchemaURL.empty())
    1934             :         {
    1935           0 :             bGotSchema = ParseXMLSchema(osXMLSchemaURL, apoFields, eGeomType);
    1936             :         }
    1937             : 
    1938         155 :         for (const auto &tileMatrix : tms->tileMatrixList())
    1939             :         {
    1940         150 :             const double dfOriX =
    1941         150 :                 bInvertAxis ? tileMatrix.mTopLeftY : tileMatrix.mTopLeftX;
    1942         150 :             const double dfOriY =
    1943         150 :                 bInvertAxis ? tileMatrix.mTopLeftX : tileMatrix.mTopLeftY;
    1944             : 
    1945         150 :             auto oLimitsIter = oMapTileMatrixSetLimits.find(tileMatrix.mId);
    1946         300 :             if (!oMapTileMatrixSetLimits.empty() &&
    1947         300 :                 oLimitsIter == oMapTileMatrixSetLimits.end())
    1948             :             {
    1949             :                 // Tile matrix level not in known limits
    1950         115 :                 continue;
    1951             :             }
    1952          35 :             int minCol = std::max(
    1953          70 :                 0, static_cast<int>((dfXMin - dfOriX) / tileMatrix.mResX /
    1954          35 :                                     tileMatrix.mTileWidth));
    1955             :             int maxCol =
    1956          70 :                 std::min(tileMatrix.mMatrixWidth - 1,
    1957          70 :                          static_cast<int>((dfXMax - dfOriX) / tileMatrix.mResX /
    1958          35 :                                           tileMatrix.mTileWidth));
    1959          35 :             int minRow = std::max(
    1960          70 :                 0, static_cast<int>((dfOriY - dfYMax) / tileMatrix.mResY /
    1961          35 :                                     tileMatrix.mTileHeight));
    1962             :             int maxRow =
    1963          70 :                 std::min(tileMatrix.mMatrixHeight - 1,
    1964          70 :                          static_cast<int>((dfOriY - dfYMin) / tileMatrix.mResY /
    1965          35 :                                           tileMatrix.mTileHeight));
    1966          35 :             if (oLimitsIter != oMapTileMatrixSetLimits.end())
    1967             :             {
    1968             :                 // Take into account tileMatrixSetLimits
    1969          35 :                 minCol = std::max(minCol, oLimitsIter->second.minTileCol);
    1970          35 :                 minRow = std::max(minRow, oLimitsIter->second.minTileRow);
    1971          35 :                 maxCol = std::min(maxCol, oLimitsIter->second.maxTileCol);
    1972          35 :                 maxRow = std::min(maxRow, oLimitsIter->second.maxTileRow);
    1973          35 :                 if (minCol > maxCol || minRow > maxRow)
    1974             :                 {
    1975           0 :                     continue;
    1976             :                 }
    1977             :             }
    1978             :             auto poLayer = std::make_unique<OGCAPITiledLayer>(
    1979          35 :                 this, bInvertAxis, osVectorURL, osVectorURL == osMVT_URL,
    1980          70 :                 tileMatrix, eGeomType);
    1981          35 :             poLayer->SetMinMaxXY(minCol, minRow, maxCol, maxRow);
    1982          35 :             poLayer->SetExtent(dfXMin, dfYMin, dfXMax, dfYMax);
    1983          35 :             if (bGotSchema)
    1984           0 :                 poLayer->SetFields(apoFields);
    1985          35 :             m_apoLayers.emplace_back(std::move(poLayer));
    1986             :         }
    1987             : 
    1988           5 :         bFoundSomething = true;
    1989             :     }
    1990             : 
    1991          18 :     if (!osRasterURL.empty() && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0)
    1992             :     {
    1993           8 :         if (bBBOXIsInCRS84)
    1994             :         {
    1995             :             // Reproject the extent if needed
    1996          16 :             OGRSpatialReference oCRS84;
    1997           8 :             oCRS84.importFromEPSG(4326);
    1998           8 :             oCRS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1999             :             auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    2000          16 :                 OGRCreateCoordinateTransformation(&oCRS84, &m_oSRS));
    2001           8 :             if (poCT)
    2002             :             {
    2003           8 :                 poCT->TransformBounds(dfXMin, dfYMin, dfXMax, dfYMax, &dfXMin,
    2004           8 :                                       &dfYMin, &dfXMax, &dfYMax, 21);
    2005             :             }
    2006             :         }
    2007             : 
    2008           8 :         const bool bCache = CPLTestBool(
    2009           8 :             CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
    2010           8 :         const int nMaxConnections = atoi(CSLFetchNameValueDef(
    2011           8 :             poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
    2012             :             CPLGetConfigOption("GDAL_WMS_MAX_CONNECTIONS", "5")));
    2013             :         const char *pszTileMatrix =
    2014           8 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILEMATRIX");
    2015             : 
    2016           8 :         int l_nBands = FigureBands(osContentType, osRasterURL);
    2017             : 
    2018         191 :         for (const auto &tileMatrix : tms->tileMatrixList())
    2019             :         {
    2020         191 :             if (pszTileMatrix && !EQUAL(tileMatrix.mId.c_str(), pszTileMatrix))
    2021             :             {
    2022          99 :                 continue;
    2023             :             }
    2024         191 :             if (tileMatrix.mTileWidth == 0 ||
    2025         191 :                 tileMatrix.mMatrixWidth > INT_MAX / tileMatrix.mTileWidth ||
    2026         183 :                 tileMatrix.mTileHeight == 0 ||
    2027         183 :                 tileMatrix.mMatrixHeight > INT_MAX / tileMatrix.mTileHeight)
    2028             :             {
    2029             :                 // Too resoluted for GDAL limits
    2030             :                 break;
    2031             :             }
    2032         183 :             auto oLimitsIter = oMapTileMatrixSetLimits.find(tileMatrix.mId);
    2033         366 :             if (!oMapTileMatrixSetLimits.empty() &&
    2034         366 :                 oLimitsIter == oMapTileMatrixSetLimits.end())
    2035             :             {
    2036             :                 // Tile matrix level not in known limits
    2037          99 :                 continue;
    2038             :             }
    2039             : 
    2040          84 :             if (dfXMax - dfXMin < tileMatrix.mResX ||
    2041          84 :                 dfYMax - dfYMin < tileMatrix.mResY)
    2042             :             {
    2043             :                 // skip levels for which the extent is smaller than the size
    2044             :                 // of one pixel
    2045           0 :                 continue;
    2046             :             }
    2047             : 
    2048          84 :             CPLString osURL(osRasterURL);
    2049          84 :             osURL.replaceAll("{tileMatrix}", tileMatrix.mId.c_str());
    2050          84 :             osURL.replaceAll("{tileRow}", "${y}");
    2051          84 :             osURL.replaceAll("{tileCol}", "${x}");
    2052             : 
    2053          84 :             const double dfOriX =
    2054          84 :                 bInvertAxis ? tileMatrix.mTopLeftY : tileMatrix.mTopLeftX;
    2055          84 :             const double dfOriY =
    2056          84 :                 bInvertAxis ? tileMatrix.mTopLeftX : tileMatrix.mTopLeftY;
    2057             : 
    2058             :             const auto CreateWMS_XML =
    2059          84 :                 [=, &osURL, &tileMatrix](int minRow, int rowCount,
    2060             :                                          int nCoalesce, double &dfStripMinY,
    2061         252 :                                          double &dfStripMaxY)
    2062             :             {
    2063          84 :                 int minCol = 0;
    2064          84 :                 int maxCol = tileMatrix.mMatrixWidth - 1;
    2065          84 :                 int maxRow = minRow + rowCount - 1;
    2066          84 :                 double dfStripMinX =
    2067          84 :                     dfOriX + minCol * tileMatrix.mTileWidth * tileMatrix.mResX;
    2068          84 :                 double dfStripMaxX = dfOriX + (maxCol + 1) *
    2069          84 :                                                   tileMatrix.mTileWidth *
    2070          84 :                                                   tileMatrix.mResX;
    2071          84 :                 dfStripMaxY =
    2072          84 :                     dfOriY - minRow * tileMatrix.mTileHeight * tileMatrix.mResY;
    2073          84 :                 dfStripMinY = dfOriY - (maxRow + 1) * tileMatrix.mTileHeight *
    2074          84 :                                            tileMatrix.mResY;
    2075          84 :                 CPLString osWMS_XML;
    2076          84 :                 char *pszEscapedURL = CPLEscapeString(osURL, -1, CPLES_XML);
    2077             :                 osWMS_XML.Printf(
    2078             :                     "<GDAL_WMS>"
    2079             :                     "    <Service name=\"TMS\">"
    2080             :                     "        <ServerUrl>%s</ServerUrl>"
    2081             :                     "        <TileXMultiplier>%d</TileXMultiplier>"
    2082             :                     "    </Service>"
    2083             :                     "    <DataWindow>"
    2084             :                     "        <UpperLeftX>%.17g</UpperLeftX>"
    2085             :                     "        <UpperLeftY>%.17g</UpperLeftY>"
    2086             :                     "        <LowerRightX>%.17g</LowerRightX>"
    2087             :                     "        <LowerRightY>%.17g</LowerRightY>"
    2088             :                     "        <TileLevel>0</TileLevel>"
    2089             :                     "        <TileY>%d</TileY>"
    2090             :                     "        <SizeX>%d</SizeX>"
    2091             :                     "        <SizeY>%d</SizeY>"
    2092             :                     "        <YOrigin>top</YOrigin>"
    2093             :                     "    </DataWindow>"
    2094             :                     "    <BlockSizeX>%d</BlockSizeX>"
    2095             :                     "    <BlockSizeY>%d</BlockSizeY>"
    2096             :                     "    <BandsCount>%d</BandsCount>"
    2097             :                     "    <MaxConnections>%d</MaxConnections>"
    2098             :                     "    %s"
    2099             :                     "</GDAL_WMS>",
    2100             :                     pszEscapedURL, nCoalesce, dfStripMinX, dfStripMaxY,
    2101             :                     dfStripMaxX, dfStripMinY, minRow,
    2102          84 :                     (maxCol - minCol + 1) / nCoalesce * tileMatrix.mTileWidth,
    2103          84 :                     rowCount * tileMatrix.mTileHeight, tileMatrix.mTileWidth,
    2104          84 :                     tileMatrix.mTileHeight, l_nBands, nMaxConnections,
    2105          84 :                     bCache ? "<Cache />" : "");
    2106          84 :                 CPLFree(pszEscapedURL);
    2107          84 :                 return osWMS_XML;
    2108          84 :             };
    2109             : 
    2110          84 :             auto vmwl = tileMatrix.mVariableMatrixWidthList;
    2111          84 :             if (vmwl.empty())
    2112             :             {
    2113             :                 double dfIgnored1, dfIgnored2;
    2114          84 :                 CPLString osWMS_XML(CreateWMS_XML(0, tileMatrix.mMatrixHeight,
    2115          84 :                                                   1, dfIgnored1, dfIgnored2));
    2116          84 :                 if (osWMS_XML.empty())
    2117           0 :                     continue;
    2118             :                 std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
    2119          84 :                     osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
    2120          84 :                 if (!poDS)
    2121           0 :                     return false;
    2122          84 :                 m_apoDatasetsAssembled.emplace_back(std::move(poDS));
    2123             :             }
    2124             :             else
    2125             :             {
    2126           0 :                 std::sort(vmwl.begin(), vmwl.end(),
    2127           0 :                           [](const gdal::TileMatrixSet::TileMatrix::
    2128             :                                  VariableMatrixWidth &a,
    2129             :                              const gdal::TileMatrixSet::TileMatrix::
    2130             :                                  VariableMatrixWidth &b)
    2131           0 :                           { return a.mMinTileRow < b.mMinTileRow; });
    2132           0 :                 std::vector<GDALDatasetH> apoStrippedDS;
    2133             :                 // For each variable matrix width, create a separate WMS dataset
    2134             :                 // with the correspond strip
    2135           0 :                 for (size_t i = 0; i < vmwl.size(); i++)
    2136             :                 {
    2137           0 :                     if (vmwl[i].mCoalesce <= 0 ||
    2138           0 :                         (tileMatrix.mMatrixWidth % vmwl[i].mCoalesce) != 0)
    2139             :                     {
    2140           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    2141             :                                  "Invalid coalesce factor (%d) w.r.t matrix "
    2142             :                                  "width (%d)",
    2143           0 :                                  vmwl[i].mCoalesce, tileMatrix.mMatrixWidth);
    2144           0 :                         return false;
    2145             :                     }
    2146             :                     {
    2147           0 :                         double dfStripMinY = 0;
    2148           0 :                         double dfStripMaxY = 0;
    2149             :                         CPLString osWMS_XML(CreateWMS_XML(
    2150           0 :                             vmwl[i].mMinTileRow,
    2151           0 :                             vmwl[i].mMaxTileRow - vmwl[i].mMinTileRow + 1,
    2152           0 :                             vmwl[i].mCoalesce, dfStripMinY, dfStripMaxY));
    2153           0 :                         if (osWMS_XML.empty())
    2154           0 :                             continue;
    2155           0 :                         if (dfStripMinY < dfYMax && dfStripMaxY > dfYMin)
    2156             :                         {
    2157             :                             std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
    2158           0 :                                 osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
    2159           0 :                             if (!poDS)
    2160           0 :                                 return false;
    2161             :                             m_apoDatasetsElementary.emplace_back(
    2162           0 :                                 std::move(poDS));
    2163           0 :                             apoStrippedDS.emplace_back(GDALDataset::ToHandle(
    2164           0 :                                 m_apoDatasetsElementary.back().get()));
    2165             :                         }
    2166             :                     }
    2167             : 
    2168             :                     // Add a strip for non-coalesced tiles
    2169           0 :                     if (i + 1 < vmwl.size() &&
    2170           0 :                         vmwl[i].mMaxTileRow + 1 != vmwl[i + 1].mMinTileRow)
    2171             :                     {
    2172           0 :                         double dfStripMinY = 0;
    2173           0 :                         double dfStripMaxY = 0;
    2174             :                         CPLString osWMS_XML(CreateWMS_XML(
    2175           0 :                             vmwl[i].mMaxTileRow + 1,
    2176           0 :                             vmwl[i + 1].mMinTileRow - vmwl[i].mMaxTileRow - 1,
    2177           0 :                             1, dfStripMinY, dfStripMaxY));
    2178           0 :                         if (osWMS_XML.empty())
    2179           0 :                             continue;
    2180           0 :                         if (dfStripMinY < dfYMax && dfStripMaxY > dfYMin)
    2181             :                         {
    2182             :                             std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
    2183           0 :                                 osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
    2184           0 :                             if (!poDS)
    2185           0 :                                 return false;
    2186             :                             m_apoDatasetsElementary.emplace_back(
    2187           0 :                                 std::move(poDS));
    2188           0 :                             apoStrippedDS.emplace_back(GDALDataset::ToHandle(
    2189           0 :                                 m_apoDatasetsElementary.back().get()));
    2190             :                         }
    2191             :                     }
    2192             :                 }
    2193             : 
    2194           0 :                 if (apoStrippedDS.empty())
    2195           0 :                     return false;
    2196             : 
    2197             :                 // Assemble the strips in a single VRT
    2198           0 :                 CPLStringList argv;
    2199           0 :                 argv.AddString("-resolution");
    2200           0 :                 argv.AddString("highest");
    2201             :                 GDALBuildVRTOptions *psOptions =
    2202           0 :                     GDALBuildVRTOptionsNew(argv.List(), nullptr);
    2203           0 :                 GDALDatasetH hAssembledDS = GDALBuildVRT(
    2204           0 :                     "", static_cast<int>(apoStrippedDS.size()),
    2205           0 :                     &apoStrippedDS[0], nullptr, psOptions, nullptr);
    2206           0 :                 GDALBuildVRTOptionsFree(psOptions);
    2207           0 :                 if (hAssembledDS == nullptr)
    2208           0 :                     return false;
    2209             :                 m_apoDatasetsAssembled.emplace_back(
    2210           0 :                     GDALDataset::FromHandle(hAssembledDS));
    2211             :             }
    2212             : 
    2213          84 :             CPLStringList argv;
    2214          84 :             argv.AddString("-of");
    2215          84 :             argv.AddString("VRT");
    2216          84 :             argv.AddString("-projwin");
    2217          84 :             argv.AddString(CPLSPrintf("%.17g", dfXMin));
    2218          84 :             argv.AddString(CPLSPrintf("%.17g", dfYMax));
    2219          84 :             argv.AddString(CPLSPrintf("%.17g", dfXMax));
    2220          84 :             argv.AddString(CPLSPrintf("%.17g", dfYMin));
    2221             :             GDALTranslateOptions *psOptions =
    2222          84 :                 GDALTranslateOptionsNew(argv.List(), nullptr);
    2223          84 :             GDALDatasetH hCroppedDS = GDALTranslate(
    2224          84 :                 "", GDALDataset::ToHandle(m_apoDatasetsAssembled.back().get()),
    2225             :                 psOptions, nullptr);
    2226          84 :             GDALTranslateOptionsFree(psOptions);
    2227          84 :             if (hCroppedDS == nullptr)
    2228           0 :                 return false;
    2229             :             m_apoDatasetsCropped.emplace_back(
    2230          84 :                 GDALDataset::FromHandle(hCroppedDS));
    2231             : 
    2232          84 :             if (tileMatrix.mResX <= m_gt.xscale)
    2233           0 :                 break;
    2234             :         }
    2235           8 :         if (!m_apoDatasetsCropped.empty())
    2236             :         {
    2237           8 :             std::reverse(std::begin(m_apoDatasetsCropped),
    2238           8 :                          std::end(m_apoDatasetsCropped));
    2239           8 :             nRasterXSize = m_apoDatasetsCropped[0]->GetRasterXSize();
    2240           8 :             nRasterYSize = m_apoDatasetsCropped[0]->GetRasterYSize();
    2241           8 :             m_apoDatasetsCropped[0]->GetGeoTransform(m_gt);
    2242             : 
    2243          38 :             for (int i = 1; i <= m_apoDatasetsCropped[0]->GetRasterCount(); i++)
    2244             :             {
    2245          30 :                 SetBand(i, new OGCAPITilesWrapperBand(this, i));
    2246             :             }
    2247           8 :             SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2248             : 
    2249           8 :             bFoundSomething = true;
    2250             :         }
    2251             :     }
    2252             : 
    2253          18 :     return bFoundSomething;
    2254             : }
    2255             : 
    2256             : /************************************************************************/
    2257             : /*                       OGCAPITilesWrapperBand()                       */
    2258             : /************************************************************************/
    2259             : 
    2260          30 : OGCAPITilesWrapperBand::OGCAPITilesWrapperBand(OGCAPIDataset *poDSIn,
    2261          30 :                                                int nBandIn)
    2262             : {
    2263          30 :     poDS = poDSIn;
    2264          30 :     nBand = nBandIn;
    2265          30 :     eDataType = poDSIn->m_apoDatasetsCropped[0]
    2266             :                     ->GetRasterBand(nBand)
    2267          30 :                     ->GetRasterDataType();
    2268          30 :     poDSIn->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->GetBlockSize(
    2269             :         &nBlockXSize, &nBlockYSize);
    2270          30 : }
    2271             : 
    2272             : /************************************************************************/
    2273             : /*                             IReadBlock()                             */
    2274             : /************************************************************************/
    2275             : 
    2276           1 : CPLErr OGCAPITilesWrapperBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2277             :                                           void *pImage)
    2278             : {
    2279           1 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    2280           1 :     return poGDS->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->ReadBlock(
    2281           1 :         nBlockXOff, nBlockYOff, pImage);
    2282             : }
    2283             : 
    2284             : /************************************************************************/
    2285             : /*                             IRasterIO()                              */
    2286             : /************************************************************************/
    2287             : 
    2288           0 : CPLErr OGCAPITilesWrapperBand::IRasterIO(
    2289             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    2290             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    2291             :     GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
    2292             : {
    2293           0 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    2294             : 
    2295           0 :     if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
    2296           0 :         poGDS->m_apoDatasetsCropped.size() > 1 && eRWFlag == GF_Read)
    2297             :     {
    2298             :         int bTried;
    2299           0 :         CPLErr eErr = TryOverviewRasterIO(
    2300             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2301             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
    2302           0 :         if (bTried)
    2303           0 :             return eErr;
    2304             :     }
    2305             : 
    2306           0 :     return poGDS->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->RasterIO(
    2307             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2308           0 :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
    2309             : }
    2310             : 
    2311             : /************************************************************************/
    2312             : /*                          GetOverviewCount()                          */
    2313             : /************************************************************************/
    2314             : 
    2315          10 : int OGCAPITilesWrapperBand::GetOverviewCount()
    2316             : {
    2317          10 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    2318          10 :     return static_cast<int>(poGDS->m_apoDatasetsCropped.size() - 1);
    2319             : }
    2320             : 
    2321             : /************************************************************************/
    2322             : /*                            GetOverview()                             */
    2323             : /************************************************************************/
    2324             : 
    2325           1 : GDALRasterBand *OGCAPITilesWrapperBand::GetOverview(int nLevel)
    2326             : {
    2327           1 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    2328           1 :     if (nLevel < 0 || nLevel >= GetOverviewCount())
    2329           0 :         return nullptr;
    2330           1 :     return poGDS->m_apoDatasetsCropped[nLevel + 1]->GetRasterBand(nBand);
    2331             : }
    2332             : 
    2333             : /************************************************************************/
    2334             : /*                       GetColorInterpretation()                       */
    2335             : /************************************************************************/
    2336             : 
    2337           5 : GDALColorInterp OGCAPITilesWrapperBand::GetColorInterpretation()
    2338             : {
    2339           5 :     OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
    2340           5 :     return poGDS->m_apoDatasetsCropped[0]
    2341           5 :         ->GetRasterBand(nBand)
    2342           5 :         ->GetColorInterpretation();
    2343             : }
    2344             : 
    2345             : /************************************************************************/
    2346             : /*                             IRasterIO()                              */
    2347             : /************************************************************************/
    2348             : 
    2349           2 : CPLErr OGCAPIDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    2350             :                                 int nXSize, int nYSize, void *pData,
    2351             :                                 int nBufXSize, int nBufYSize,
    2352             :                                 GDALDataType eBufType, int nBandCount,
    2353             :                                 BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    2354             :                                 GSpacing nLineSpace, GSpacing nBandSpace,
    2355             :                                 GDALRasterIOExtraArg *psExtraArg)
    2356             : {
    2357           2 :     if (!m_apoDatasetsCropped.empty())
    2358             :     {
    2359             :         // Tiles API
    2360           1 :         if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
    2361           2 :             m_apoDatasetsCropped.size() > 1 && eRWFlag == GF_Read)
    2362             :         {
    2363             :             int bTried;
    2364           0 :             CPLErr eErr = TryOverviewRasterIO(
    2365             :                 eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
    2366             :                 nBufYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
    2367             :                 nLineSpace, nBandSpace, psExtraArg, &bTried);
    2368           0 :             if (bTried)
    2369           0 :                 return eErr;
    2370             :         }
    2371             : 
    2372           1 :         return m_apoDatasetsCropped[0]->RasterIO(
    2373             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2374             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
    2375           1 :             nBandSpace, psExtraArg);
    2376             :     }
    2377           1 :     else if (m_poWMSDS)
    2378             :     {
    2379             :         // Maps API
    2380           1 :         return m_poWMSDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    2381             :                                    nBufXSize, nBufYSize, eBufType, nBandCount,
    2382             :                                    panBandMap, nPixelSpace, nLineSpace,
    2383           1 :                                    nBandSpace, psExtraArg);
    2384             :     }
    2385             : 
    2386             :     // Should not be hit
    2387           0 :     return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    2388             :                                   nBufXSize, nBufYSize, eBufType, nBandCount,
    2389             :                                   panBandMap, nPixelSpace, nLineSpace,
    2390           0 :                                   nBandSpace, psExtraArg);
    2391             : }
    2392             : 
    2393             : /************************************************************************/
    2394             : /*                          OGCAPITiledLayer()                          */
    2395             : /************************************************************************/
    2396             : 
    2397          35 : OGCAPITiledLayer::OGCAPITiledLayer(
    2398             :     OGCAPIDataset *poDS, bool bInvertAxis, const CPLString &osTileURL,
    2399             :     bool bIsMVT, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
    2400          35 :     OGRwkbGeometryType eGeomType)
    2401             :     : m_poDS(poDS), m_osTileURL(osTileURL), m_bIsMVT(bIsMVT),
    2402          35 :       m_oTileMatrix(tileMatrix), m_bInvertAxis(bInvertAxis)
    2403             : {
    2404          35 :     m_poFeatureDefn = new OGCAPITiledLayerFeatureDefn(
    2405          35 :         this, ("Zoom level " + tileMatrix.mId).c_str());
    2406          35 :     SetDescription(m_poFeatureDefn->GetName());
    2407          35 :     m_poFeatureDefn->SetGeomType(eGeomType);
    2408          35 :     if (eGeomType != wkbNone)
    2409             :     {
    2410          35 :         auto poClonedSRS = poDS->m_oSRS.Clone();
    2411          35 :         m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poClonedSRS);
    2412          35 :         poClonedSRS->Dereference();
    2413             :     }
    2414          35 :     m_poFeatureDefn->Reference();
    2415          35 :     m_osTileURL.replaceAll("{tileMatrix}", tileMatrix.mId.c_str());
    2416          35 : }
    2417             : 
    2418             : /************************************************************************/
    2419             : /*                         ~OGCAPITiledLayer()                          */
    2420             : /************************************************************************/
    2421             : 
    2422          70 : OGCAPITiledLayer::~OGCAPITiledLayer()
    2423             : {
    2424          35 :     m_poFeatureDefn->InvalidateLayer();
    2425          35 :     m_poFeatureDefn->Release();
    2426          70 : }
    2427             : 
    2428             : /************************************************************************/
    2429             : /*                      GetCoalesceFactorForRow()                       */
    2430             : /************************************************************************/
    2431             : 
    2432          40 : int OGCAPITiledLayer::GetCoalesceFactorForRow(int nRow) const
    2433             : {
    2434          40 :     int nCoalesce = 1;
    2435          40 :     for (const auto &vmw : m_oTileMatrix.mVariableMatrixWidthList)
    2436             :     {
    2437           0 :         if (nRow >= vmw.mMinTileRow && nRow <= vmw.mMaxTileRow)
    2438             :         {
    2439           0 :             nCoalesce = vmw.mCoalesce;
    2440           0 :             break;
    2441             :         }
    2442             :     }
    2443          40 :     return nCoalesce;
    2444             : }
    2445             : 
    2446             : /************************************************************************/
    2447             : /*                            ResetReading()                            */
    2448             : /************************************************************************/
    2449             : 
    2450          35 : void OGCAPITiledLayer::ResetReading()
    2451             : {
    2452          35 :     if (m_nCurX == m_nCurMinX && m_nCurY == m_nCurMinY && m_poUnderlyingLayer)
    2453             :     {
    2454           0 :         m_poUnderlyingLayer->ResetReading();
    2455             :     }
    2456             :     else
    2457             :     {
    2458          35 :         m_nCurX = m_nCurMinX;
    2459          35 :         m_nCurY = m_nCurMinY;
    2460          35 :         m_poUnderlyingDS.reset();
    2461          35 :         m_poUnderlyingLayer = nullptr;
    2462             :     }
    2463          35 : }
    2464             : 
    2465             : /************************************************************************/
    2466             : /*                              OpenTile()                              */
    2467             : /************************************************************************/
    2468             : 
    2469          20 : GDALDataset *OGCAPITiledLayer::OpenTile(int nX, int nY, bool &bEmptyContent)
    2470             : {
    2471          20 :     int nCoalesce = GetCoalesceFactorForRow(nY);
    2472          20 :     if (nCoalesce <= 0)
    2473           0 :         return nullptr;
    2474          20 :     nX = (nX / nCoalesce) * nCoalesce;
    2475             : 
    2476          20 :     const char *const *papszOpenOptions = nullptr;
    2477          40 :     CPLString poPrefix;
    2478          40 :     CPLStringList aosOpenOptions;
    2479             : 
    2480          20 :     if (m_bIsMVT)
    2481             :     {
    2482          12 :         const double dfOriX =
    2483          12 :             m_bInvertAxis ? m_oTileMatrix.mTopLeftY : m_oTileMatrix.mTopLeftX;
    2484          12 :         const double dfOriY =
    2485          12 :             m_bInvertAxis ? m_oTileMatrix.mTopLeftX : m_oTileMatrix.mTopLeftY;
    2486             :         aosOpenOptions.SetNameValue(
    2487             :             "@GEOREF_TOPX",
    2488          12 :             CPLSPrintf("%.17g", dfOriX + nX * m_oTileMatrix.mResX *
    2489          12 :                                              m_oTileMatrix.mTileWidth));
    2490             :         aosOpenOptions.SetNameValue(
    2491             :             "@GEOREF_TOPY",
    2492          12 :             CPLSPrintf("%.17g", dfOriY - nY * m_oTileMatrix.mResY *
    2493          12 :                                              m_oTileMatrix.mTileHeight));
    2494             :         aosOpenOptions.SetNameValue(
    2495             :             "@GEOREF_TILEDIMX",
    2496          12 :             CPLSPrintf("%.17g", nCoalesce * m_oTileMatrix.mResX *
    2497          12 :                                     m_oTileMatrix.mTileWidth));
    2498             :         aosOpenOptions.SetNameValue(
    2499             :             "@GEOREF_TILEDIMY",
    2500             :             CPLSPrintf("%.17g",
    2501          12 :                        m_oTileMatrix.mResY * m_oTileMatrix.mTileWidth));
    2502             : 
    2503          12 :         papszOpenOptions = aosOpenOptions.List();
    2504          12 :         poPrefix = "MVT";
    2505             :     }
    2506             : 
    2507          20 :     std::unique_ptr<GDALDataset> dataset = m_poDS->OpenTile(
    2508          40 :         m_osTileURL, stoi(m_oTileMatrix.mId), nX, nY, bEmptyContent,
    2509          40 :         GDAL_OF_VECTOR, poPrefix, papszOpenOptions);
    2510             : 
    2511          20 :     return dataset.release();
    2512             : }
    2513             : 
    2514             : /************************************************************************/
    2515             : /*                    FinalizeFeatureDefnWithLayer()                    */
    2516             : /************************************************************************/
    2517             : 
    2518           5 : void OGCAPITiledLayer::FinalizeFeatureDefnWithLayer(OGRLayer *poUnderlyingLayer)
    2519             : {
    2520           5 :     if (!m_bFeatureDefnEstablished)
    2521             :     {
    2522           5 :         m_bFeatureDefnEstablished = true;
    2523           5 :         const auto poSrcFieldDefn = poUnderlyingLayer->GetLayerDefn();
    2524           5 :         const int nFieldCount = poSrcFieldDefn->GetFieldCount();
    2525          60 :         for (int i = 0; i < nFieldCount; i++)
    2526             :         {
    2527          55 :             m_poFeatureDefn->AddFieldDefn(poSrcFieldDefn->GetFieldDefn(i));
    2528             :         }
    2529             :     }
    2530           5 : }
    2531             : 
    2532             : /************************************************************************/
    2533             : /*                            BuildFeature()                            */
    2534             : /************************************************************************/
    2535             : 
    2536             : OGRFeature *
    2537           5 : OGCAPITiledLayer::BuildFeature(std::unique_ptr<OGRFeature> poSrcFeature, int nX,
    2538             :                                int nY)
    2539             : {
    2540           5 :     int nCoalesce = GetCoalesceFactorForRow(nY);
    2541           5 :     if (nCoalesce <= 0)
    2542           0 :         return nullptr;
    2543           5 :     nX = (nX / nCoalesce) * nCoalesce;
    2544             : 
    2545           5 :     OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
    2546           5 :     const GIntBig nFID = nY * m_oTileMatrix.mMatrixWidth + nX +
    2547           5 :                          poSrcFeature->GetFID() * m_oTileMatrix.mMatrixWidth *
    2548           5 :                              m_oTileMatrix.mMatrixHeight;
    2549           5 :     auto poGeom = std::unique_ptr<OGRGeometry>(poSrcFeature->StealGeometry());
    2550           5 :     if (poGeom && m_poFeatureDefn->GetGeomType() != wkbUnknown)
    2551             :     {
    2552           0 :         poGeom = OGRGeometryFactory::forceTo(std::move(poGeom),
    2553           0 :                                              m_poFeatureDefn->GetGeomType());
    2554             :     }
    2555           5 :     poFeature->SetFrom(poSrcFeature.get(), true);
    2556           5 :     poFeature->SetFID(nFID);
    2557           5 :     if (poGeom && m_poFeatureDefn->GetGeomFieldCount() > 0)
    2558             :     {
    2559          10 :         poGeom->assignSpatialReference(
    2560           5 :             m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
    2561             :     }
    2562           5 :     poFeature->SetGeometry(std::move(poGeom));
    2563           5 :     return poFeature;
    2564             : }
    2565             : 
    2566             : /************************************************************************/
    2567             : /*                        IncrementTileIndices()                        */
    2568             : /************************************************************************/
    2569             : 
    2570          15 : bool OGCAPITiledLayer::IncrementTileIndices()
    2571             : {
    2572             : 
    2573          15 :     const int nCoalesce = GetCoalesceFactorForRow(m_nCurY);
    2574          15 :     if (nCoalesce <= 0)
    2575           0 :         return false;
    2576          15 :     if (m_nCurX / nCoalesce < m_nCurMaxX / nCoalesce)
    2577             :     {
    2578          15 :         m_nCurX += nCoalesce;
    2579             :     }
    2580           0 :     else if (m_nCurY < m_nCurMaxY)
    2581             :     {
    2582           0 :         m_nCurX = m_nCurMinX;
    2583           0 :         m_nCurY++;
    2584             :     }
    2585             :     else
    2586             :     {
    2587           0 :         m_nCurY = -1;
    2588           0 :         return false;
    2589             :     }
    2590          15 :     return true;
    2591             : }
    2592             : 
    2593             : /************************************************************************/
    2594             : /*                         GetNextRawFeature()                          */
    2595             : /************************************************************************/
    2596             : 
    2597          20 : OGRFeature *OGCAPITiledLayer::GetNextRawFeature()
    2598             : {
    2599             :     while (true)
    2600             :     {
    2601          20 :         if (m_poUnderlyingLayer == nullptr)
    2602             :         {
    2603          20 :             if (m_nCurY < 0)
    2604             :             {
    2605           0 :                 return nullptr;
    2606             :             }
    2607          20 :             bool bEmptyContent = false;
    2608          20 :             m_poUnderlyingDS.reset(OpenTile(m_nCurX, m_nCurY, bEmptyContent));
    2609          20 :             if (bEmptyContent)
    2610             :             {
    2611          15 :                 if (!IncrementTileIndices())
    2612           0 :                     return nullptr;
    2613          15 :                 continue;
    2614             :             }
    2615           5 :             if (m_poUnderlyingDS == nullptr)
    2616             :             {
    2617           0 :                 return nullptr;
    2618             :             }
    2619           5 :             m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
    2620           5 :             if (m_poUnderlyingLayer == nullptr)
    2621             :             {
    2622           0 :                 return nullptr;
    2623             :             }
    2624           5 :             FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
    2625             :         }
    2626             : 
    2627             :         auto poSrcFeature =
    2628           5 :             std::unique_ptr<OGRFeature>(m_poUnderlyingLayer->GetNextFeature());
    2629           5 :         if (poSrcFeature != nullptr)
    2630             :         {
    2631           5 :             return BuildFeature(std::move(poSrcFeature), m_nCurX, m_nCurY);
    2632             :         }
    2633             : 
    2634           0 :         m_poUnderlyingDS.reset();
    2635           0 :         m_poUnderlyingLayer = nullptr;
    2636             : 
    2637           0 :         if (!IncrementTileIndices())
    2638           0 :             return nullptr;
    2639          15 :     }
    2640             : }
    2641             : 
    2642             : /************************************************************************/
    2643             : /*                             GetFeature()                             */
    2644             : /************************************************************************/
    2645             : 
    2646           0 : OGRFeature *OGCAPITiledLayer::GetFeature(GIntBig nFID)
    2647             : {
    2648           0 :     if (nFID < 0)
    2649           0 :         return nullptr;
    2650           0 :     const GIntBig nFIDInTile =
    2651           0 :         nFID / (m_oTileMatrix.mMatrixWidth * m_oTileMatrix.mMatrixHeight);
    2652           0 :     const GIntBig nTileID =
    2653           0 :         nFID % (m_oTileMatrix.mMatrixWidth * m_oTileMatrix.mMatrixHeight);
    2654           0 :     const int nY = static_cast<int>(nTileID / m_oTileMatrix.mMatrixWidth);
    2655           0 :     const int nX = static_cast<int>(nTileID % m_oTileMatrix.mMatrixWidth);
    2656           0 :     bool bEmptyContent = false;
    2657             :     std::unique_ptr<GDALDataset> poUnderlyingDS(
    2658           0 :         OpenTile(nX, nY, bEmptyContent));
    2659           0 :     if (poUnderlyingDS == nullptr)
    2660           0 :         return nullptr;
    2661           0 :     OGRLayer *poUnderlyingLayer = poUnderlyingDS->GetLayer(0);
    2662           0 :     if (poUnderlyingLayer == nullptr)
    2663           0 :         return nullptr;
    2664           0 :     FinalizeFeatureDefnWithLayer(poUnderlyingLayer);
    2665             :     auto poSrcFeature =
    2666           0 :         std::unique_ptr<OGRFeature>(poUnderlyingLayer->GetFeature(nFIDInTile));
    2667           0 :     if (poSrcFeature == nullptr)
    2668           0 :         return nullptr;
    2669           0 :     return BuildFeature(std::move(poSrcFeature), nX, nY);
    2670             : }
    2671             : 
    2672             : /************************************************************************/
    2673             : /*                          EstablishFields()                           */
    2674             : /************************************************************************/
    2675             : 
    2676         110 : void OGCAPITiledLayer::EstablishFields()
    2677             : {
    2678         110 :     if (!m_bFeatureDefnEstablished && !m_bEstablishFieldsCalled)
    2679             :     {
    2680           0 :         m_bEstablishFieldsCalled = true;
    2681             : 
    2682             :         // Try up to 10 requests in order. We could probably remove that
    2683             :         // to use just the fallback logic.
    2684           0 :         for (int i = 0; i < 10; ++i)
    2685             :         {
    2686           0 :             bool bEmptyContent = false;
    2687           0 :             m_poUnderlyingDS.reset(OpenTile(m_nCurX, m_nCurY, bEmptyContent));
    2688           0 :             if (bEmptyContent || !m_poUnderlyingDS)
    2689             :             {
    2690           0 :                 if (!IncrementTileIndices())
    2691           0 :                     break;
    2692           0 :                 continue;
    2693             :             }
    2694           0 :             m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
    2695           0 :             if (m_poUnderlyingLayer)
    2696             :             {
    2697           0 :                 FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
    2698           0 :                 break;
    2699             :             }
    2700             :         }
    2701             : 
    2702           0 :         if (!m_bFeatureDefnEstablished)
    2703             :         {
    2704             :             // Try to sample at different locations in the extent
    2705           0 :             for (int j = 0; !m_bFeatureDefnEstablished && j < 3; ++j)
    2706             :             {
    2707           0 :                 m_nCurY = m_nMinY + (2 * j + 1) * (m_nMaxY - m_nMinY) / 6;
    2708           0 :                 for (int i = 0; i < 3; ++i)
    2709             :                 {
    2710           0 :                     m_nCurX = m_nMinX + (2 * i + 1) * (m_nMaxX - m_nMinX) / 6;
    2711           0 :                     bool bEmptyContent = false;
    2712           0 :                     m_poUnderlyingDS.reset(
    2713             :                         OpenTile(m_nCurX, m_nCurY, bEmptyContent));
    2714           0 :                     if (bEmptyContent || !m_poUnderlyingDS)
    2715             :                     {
    2716           0 :                         continue;
    2717             :                     }
    2718           0 :                     m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
    2719           0 :                     if (m_poUnderlyingLayer)
    2720             :                     {
    2721           0 :                         FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
    2722           0 :                         break;
    2723             :                     }
    2724             :                 }
    2725             :             }
    2726             :         }
    2727             : 
    2728           0 :         if (!m_bFeatureDefnEstablished)
    2729             :         {
    2730           0 :             CPLDebug("OGCAPI", "Could not establish feature definition. No "
    2731             :                                "valid tile found in sampling done");
    2732             :         }
    2733             : 
    2734           0 :         ResetReading();
    2735             :     }
    2736         110 : }
    2737             : 
    2738             : /************************************************************************/
    2739             : /*                             SetExtent()                              */
    2740             : /************************************************************************/
    2741             : 
    2742          35 : void OGCAPITiledLayer::SetExtent(double dfXMin, double dfYMin, double dfXMax,
    2743             :                                  double dfYMax)
    2744             : {
    2745          35 :     m_sEnvelope.MinX = dfXMin;
    2746          35 :     m_sEnvelope.MinY = dfYMin;
    2747          35 :     m_sEnvelope.MaxX = dfXMax;
    2748          35 :     m_sEnvelope.MaxY = dfYMax;
    2749          35 : }
    2750             : 
    2751             : /************************************************************************/
    2752             : /*                             IGetExtent()                             */
    2753             : /************************************************************************/
    2754             : 
    2755           0 : OGRErr OGCAPITiledLayer::IGetExtent(int /* iGeomField */, OGREnvelope *psExtent,
    2756             :                                     bool /* bForce */)
    2757             : {
    2758           0 :     *psExtent = m_sEnvelope;
    2759           0 :     return OGRERR_NONE;
    2760             : }
    2761             : 
    2762             : /************************************************************************/
    2763             : /*                         ISetSpatialFilter()                          */
    2764             : /************************************************************************/
    2765             : 
    2766           0 : OGRErr OGCAPITiledLayer::ISetSpatialFilter(int iGeomField,
    2767             :                                            const OGRGeometry *poGeomIn)
    2768             : {
    2769           0 :     const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
    2770           0 :     if (eErr == OGRERR_NONE)
    2771             :     {
    2772           0 :         OGREnvelope sEnvelope;
    2773           0 :         if (m_poFilterGeom != nullptr)
    2774           0 :             sEnvelope = m_sFilterEnvelope;
    2775             :         else
    2776           0 :             sEnvelope = m_sEnvelope;
    2777             : 
    2778           0 :         const double dfTileDim = m_oTileMatrix.mResX * m_oTileMatrix.mTileWidth;
    2779           0 :         const double dfOriX =
    2780           0 :             m_bInvertAxis ? m_oTileMatrix.mTopLeftY : m_oTileMatrix.mTopLeftX;
    2781           0 :         const double dfOriY =
    2782           0 :             m_bInvertAxis ? m_oTileMatrix.mTopLeftX : m_oTileMatrix.mTopLeftY;
    2783           0 :         if (sEnvelope.MinX - dfOriX >= -10 * dfTileDim &&
    2784           0 :             dfOriY - sEnvelope.MinY >= -10 * dfTileDim &&
    2785           0 :             sEnvelope.MaxX - dfOriX <= 10 * dfTileDim &&
    2786           0 :             dfOriY - sEnvelope.MaxY <= 10 * dfTileDim)
    2787             :         {
    2788           0 :             m_nCurMinX = std::max(
    2789           0 :                 m_nMinX,
    2790           0 :                 static_cast<int>(floor((sEnvelope.MinX - dfOriX) / dfTileDim)));
    2791           0 :             m_nCurMinY = std::max(
    2792           0 :                 m_nMinY,
    2793           0 :                 static_cast<int>(floor((dfOriY - sEnvelope.MaxY) / dfTileDim)));
    2794           0 :             m_nCurMaxX = std::min(
    2795           0 :                 m_nMaxX,
    2796           0 :                 static_cast<int>(floor((sEnvelope.MaxX - dfOriX) / dfTileDim)));
    2797           0 :             m_nCurMaxY = std::min(
    2798           0 :                 m_nMaxY,
    2799           0 :                 static_cast<int>(floor((dfOriY - sEnvelope.MinY) / dfTileDim)));
    2800             :         }
    2801             :         else
    2802             :         {
    2803           0 :             m_nCurMinX = m_nMinX;
    2804           0 :             m_nCurMinY = m_nMinY;
    2805           0 :             m_nCurMaxX = m_nMaxX;
    2806           0 :             m_nCurMaxY = m_nMaxY;
    2807             :         }
    2808             : 
    2809           0 :         ResetReading();
    2810             :     }
    2811           0 :     return eErr;
    2812             : }
    2813             : 
    2814             : /************************************************************************/
    2815             : /*                           TestCapability()                           */
    2816             : /************************************************************************/
    2817             : 
    2818           0 : int OGCAPITiledLayer::TestCapability(const char *pszCap) const
    2819             : {
    2820           0 :     if (EQUAL(pszCap, OLCRandomRead))
    2821           0 :         return true;
    2822           0 :     if (EQUAL(pszCap, OLCFastGetExtent))
    2823           0 :         return true;
    2824           0 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
    2825           0 :         return true;
    2826           0 :     if (EQUAL(pszCap, OLCFastSpatialFilter))
    2827           0 :         return true;
    2828           0 :     return false;
    2829             : }
    2830             : 
    2831             : /************************************************************************/
    2832             : /*                            SetMinMaxXY()                             */
    2833             : /************************************************************************/
    2834             : 
    2835          35 : void OGCAPITiledLayer::SetMinMaxXY(int minCol, int minRow, int maxCol,
    2836             :                                    int maxRow)
    2837             : {
    2838          35 :     m_nMinX = minCol;
    2839          35 :     m_nMinY = minRow;
    2840          35 :     m_nMaxX = maxCol;
    2841          35 :     m_nMaxY = maxRow;
    2842          35 :     m_nCurMinX = m_nMinX;
    2843          35 :     m_nCurMinY = m_nMinY;
    2844          35 :     m_nCurMaxX = m_nMaxX;
    2845          35 :     m_nCurMaxY = m_nMaxY;
    2846          35 :     ResetReading();
    2847          35 : }
    2848             : 
    2849             : /************************************************************************/
    2850             : /*                             SetFields()                              */
    2851             : /************************************************************************/
    2852             : 
    2853           0 : void OGCAPITiledLayer::SetFields(
    2854             :     const std::vector<std::unique_ptr<OGRFieldDefn>> &apoFields)
    2855             : {
    2856           0 :     m_bFeatureDefnEstablished = true;
    2857           0 :     for (const auto &poField : apoFields)
    2858             :     {
    2859           0 :         m_poFeatureDefn->AddFieldDefn(poField.get());
    2860             :     }
    2861           0 : }
    2862             : 
    2863             : /************************************************************************/
    2864             : /*                                Open()                                */
    2865             : /************************************************************************/
    2866             : 
    2867          35 : GDALDataset *OGCAPIDataset::Open(GDALOpenInfo *poOpenInfo)
    2868             : {
    2869          35 :     if (!Identify(poOpenInfo))
    2870           0 :         return nullptr;
    2871          70 :     auto poDS = std::make_unique<OGCAPIDataset>();
    2872          35 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:") ||
    2873           6 :         STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
    2874           0 :         STARTS_WITH(poOpenInfo->pszFilename, "https://"))
    2875             :     {
    2876          35 :         if (!poDS->InitFromURL(poOpenInfo))
    2877           8 :             return nullptr;
    2878             :     }
    2879             :     else
    2880             :     {
    2881           0 :         if (!poDS->InitFromFile(poOpenInfo))
    2882           0 :             return nullptr;
    2883             :     }
    2884          27 :     return poDS.release();
    2885             : }
    2886             : 
    2887             : /************************************************************************/
    2888             : /*                        GDALRegister_OGCAPI()                         */
    2889             : /************************************************************************/
    2890             : 
    2891        2059 : void GDALRegister_OGCAPI()
    2892             : 
    2893             : {
    2894        2059 :     if (GDALGetDriverByName("OGCAPI") != nullptr)
    2895         283 :         return;
    2896             : 
    2897        1776 :     GDALDriver *poDriver = new GDALDriver();
    2898             : 
    2899        1776 :     poDriver->SetDescription("OGCAPI");
    2900        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    2901        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    2902        1776 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGCAPI");
    2903             : 
    2904        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    2905             : 
    2906        1776 :     poDriver->SetMetadataItem(
    2907             :         GDAL_DMD_OPENOPTIONLIST,
    2908             :         "<OpenOptionList>"
    2909             :         "  <Option name='API' type='string-select' "
    2910             :         "description='Which API to use to access data' default='AUTO'>"
    2911             :         "       <Value>AUTO</Value>"
    2912             :         "       <Value>MAP</Value>"
    2913             :         "       <Value>TILES</Value>"
    2914             :         "       <Value>COVERAGE</Value>"
    2915             :         "       <Value>ITEMS</Value>"
    2916             :         "  </Option>"
    2917             :         "  <Option name='IMAGE_FORMAT' scope='raster' type='string-select' "
    2918             :         "description='Which format to use for pixel acquisition' "
    2919             :         "default='AUTO'>"
    2920             :         "       <Value>AUTO</Value>"
    2921             :         "       <Value>PNG</Value>"
    2922             :         "       <Value>PNG_PREFERRED</Value>"
    2923             :         "       <Value>JPEG</Value>"
    2924             :         "       <Value>JPEG_PREFERRED</Value>"
    2925             :         "       <Value>GEOTIFF</Value>"
    2926             :         "  </Option>"
    2927             :         "  <Option name='VECTOR_FORMAT' scope='vector' type='string-select' "
    2928             :         "description='Which format to use for vector data acquisition' "
    2929             :         "default='AUTO'>"
    2930             :         "       <Value>AUTO</Value>"
    2931             :         "       <Value>GEOJSON</Value>"
    2932             :         "       <Value>GEOJSON_PREFERRED</Value>"
    2933             :         "       <Value>MVT</Value>"
    2934             :         "       <Value>MVT_PREFERRED</Value>"
    2935             :         "  </Option>"
    2936             :         "  <Option name='TILEMATRIXSET' type='string' "
    2937             :         "description='Identifier of the required tile matrix set'/>"
    2938             :         "  <Option name='PREFERRED_TILEMATRIXSET' type='string' "
    2939             :         "description='dentifier of the preferred tile matrix set' "
    2940             :         "default='WorldCRS84Quad'/>"
    2941             :         "  <Option name='TILEMATRIX' scope='raster' type='string' "
    2942             :         "description='Tile matrix identifier.'/>"
    2943             :         "  <Option name='CACHE' scope='raster' type='boolean' "
    2944             :         "description='Whether to enable block/tile caching' default='YES'/>"
    2945             :         "  <Option name='MAX_CONNECTIONS' scope='raster' type='int' "
    2946             :         "description='Maximum number of connections' default='5'/>"
    2947             :         "  <Option name='MINX' type='float' "
    2948             :         "description='Minimum value (in SRS of TileMatrixSet) of X'/>"
    2949             :         "  <Option name='MINY' type='float' "
    2950             :         "description='Minimum value (in SRS of TileMatrixSet) of Y'/>"
    2951             :         "  <Option name='MAXX' type='float' "
    2952             :         "description='Maximum value (in SRS of TileMatrixSet) of X'/>"
    2953             :         "  <Option name='MAXY' type='float' "
    2954             :         "description='Maximum value (in SRS of TileMatrixSet) of Y'/>"
    2955        1776 :         "</OpenOptionList>");
    2956             : 
    2957        1776 :     poDriver->pfnIdentify = OGCAPIDataset::Identify;
    2958        1776 :     poDriver->pfnOpen = OGCAPIDataset::Open;
    2959             : 
    2960        1776 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2961             : }

Generated by: LCOV version 1.14