LCOV - code coverage report
Current view: top level - frmts/ogcapi - gdalogcapidataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 912 1308 69.7 %
Date: 2024-05-14 23:54:21 Functions: 55 72 76.4 %

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

Generated by: LCOV version 1.14