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