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