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