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