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