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