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