Coverage Report

Created: 2025-07-23 09:13

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