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