• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.99
/frmts/ogcapi/gdalogcapidataset.cpp
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  OGC API interface
5
 * Author:   Even Rouault, <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "cpl_error.h"
14
#include "cpl_json.h"
15
#include "cpl_http.h"
16
#include "gdal_priv.h"
17
#include "tilematrixset.hpp"
18
#include "gdal_utils.h"
19
#include "ogrsf_frmts.h"
20
#include "ogr_spatialref.h"
21

22
#include "parsexsd.h"
23

24
#include <algorithm>
25
#include <memory>
26
#include <vector>
27

28
// g++ -Wall -Wextra -std=c++11 -Wall -g -fPIC
29
// frmts/ogcapi/gdalogcapidataset.cpp -shared -o gdal_OGCAPI.so -Iport -Igcore
30
// -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gml -Iapps -L. -lgdal
31

32
extern "C" void GDALRegister_OGCAPI();
33

34
#define MEDIA_TYPE_OAPI_3_0 "application/vnd.oai.openapi+json;version=3.0"
35
#define MEDIA_TYPE_OAPI_3_0_ALT "application/openapi+json;version=3.0"
36
#define MEDIA_TYPE_JSON "application/json"
37
#define MEDIA_TYPE_GEOJSON "application/geo+json"
38
#define MEDIA_TYPE_TEXT_XML "text/xml"
39
#define MEDIA_TYPE_APPLICATION_XML "application/xml"
40
#define MEDIA_TYPE_JSON_SCHEMA "application/schema+json"
41

42
/************************************************************************/
43
/* ==================================================================== */
44
/*                           OGCAPIDataset                              */
45
/* ==================================================================== */
46
/************************************************************************/
47

48
class OGCAPIDataset final : public GDALDataset
49
{
50
    friend class OGCAPIMapWrapperBand;
51
    friend class OGCAPITilesWrapperBand;
52
    friend class OGCAPITiledLayer;
53

54
    bool m_bMustCleanPersistent = false;
55
    CPLString m_osRootURL{};
56
    CPLString m_osUserPwd{};
57
    CPLString m_osUserQueryParams{};
58
    GDALGeoTransform m_gt{};
59

60
    OGRSpatialReference m_oSRS{};
61
    CPLString m_osTileData{};
62

63
    // Classic OGC API features /items access
64
    std::unique_ptr<GDALDataset> m_poOAPIFDS{};
65

66
    // Map API
67
    std::unique_ptr<GDALDataset> m_poWMSDS{};
68

69
    // Tiles API
70
    std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsElementary{};
71
    std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsAssembled{};
72
    std::vector<std::unique_ptr<GDALDataset>> m_apoDatasetsCropped{};
73

74
    std::vector<std::unique_ptr<OGRLayer>> m_apoLayers{};
75

76
    CPLString BuildURL(const std::string &href) const;
77
    void SetRootURLFromURL(const std::string &osURL);
78
    int FigureBands(const std::string &osContentType,
79
                    const CPLString &osImageURL);
80

81
    bool InitFromFile(GDALOpenInfo *poOpenInfo);
82
    bool InitFromURL(GDALOpenInfo *poOpenInfo);
83
    bool ProcessScale(const CPLJSONObject &oScaleDenominator,
84
                      const double dfXMin, const double dfYMin,
85
                      const double dfXMax, const double dfYMax);
86
    bool InitFromCollection(GDALOpenInfo *poOpenInfo, CPLJSONDocument &oDoc);
87
    bool Download(const CPLString &osURL, const char *pszPostContent,
88
                  const char *pszAccept, CPLString &osResult,
89
                  CPLString &osContentType, bool bEmptyContentOK,
90
                  CPLStringList *paosHeaders);
91

92
    bool DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
93
                      const char *pszPostContent = nullptr,
94
                      const char *pszAccept = MEDIA_TYPE_GEOJSON
95
                      ", " MEDIA_TYPE_JSON,
96
                      CPLStringList *paosHeaders = nullptr);
97

98
    std::unique_ptr<GDALDataset>
99
    OpenTile(const CPLString &osURLPattern, int nMatrix, int nColumn, int nRow,
100
             bool &bEmptyContent, unsigned int nOpenTileFlags = 0,
101
             const CPLString &osPrefix = {},
102
             const char *const *papszOpenOptions = nullptr);
103

104
    bool InitWithMapAPI(GDALOpenInfo *poOpenInfo,
105
                        const CPLJSONObject &oCollection, double dfXMin,
106
                        double dfYMin, double dfXMax, double dfYMax);
107
    bool InitWithTilesAPI(GDALOpenInfo *poOpenInfo, const CPLString &osTilesURL,
108
                          bool bIsMap, double dfXMin, double dfYMin,
109
                          double dfXMax, double dfYMax, bool bBBOXIsInCRS84,
110
                          const CPLJSONObject &oJsonCollection);
111
    bool InitWithCoverageAPI(GDALOpenInfo *poOpenInfo,
112
                             const CPLString &osTilesURL, double dfXMin,
113
                             double dfYMin, double dfXMax, double dfYMax,
114
                             const CPLJSONObject &oJsonCollection);
115

116
  protected:
117
    CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
118
                     int nYSize, void *pData, int nBufXSize, int nBufYSize,
119
                     GDALDataType eBufType, int nBandCount,
120
                     BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
121
                     GSpacing nLineSpace, GSpacing nBandSpace,
122
                     GDALRasterIOExtraArg *psExtraArg) override;
123

124
    int CloseDependentDatasets() override;
125

126
  public:
127
    OGCAPIDataset() = default;
35✔
128
    ~OGCAPIDataset();
129

130
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
131
    const OGRSpatialReference *GetSpatialRef() const override;
132

133
    int GetLayerCount() override
32✔
134
    {
135
        return m_poOAPIFDS ? m_poOAPIFDS->GetLayerCount()
62✔
136
                           : static_cast<int>(m_apoLayers.size());
94✔
137
    }
138

139
    OGRLayer *GetLayer(int idx) override
17✔
140
    {
141
        return m_poOAPIFDS                         ? m_poOAPIFDS->GetLayer(idx)
32✔
142
               : idx >= 0 && idx < GetLayerCount() ? m_apoLayers[idx].get()
15✔
143
                                                   : nullptr;
34✔
144
    }
145

146
    static int Identify(GDALOpenInfo *poOpenInfo);
147
    static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
148
};
149

150
/************************************************************************/
151
/* ==================================================================== */
152
/*                      OGCAPIMapWrapperBand                            */
153
/* ==================================================================== */
154
/************************************************************************/
155

156
class OGCAPIMapWrapperBand final : public GDALRasterBand
157
{
158
  public:
159
    OGCAPIMapWrapperBand(OGCAPIDataset *poDS, int nBand);
160

161
    virtual GDALRasterBand *GetOverview(int nLevel) override;
162
    virtual int GetOverviewCount() override;
163
    virtual GDALColorInterp GetColorInterpretation() override;
164

165
  protected:
166
    virtual CPLErr IReadBlock(int nBlockXOff, int nBlockYOff,
167
                              void *pImage) override;
168
    virtual CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
169
                             GDALDataType, GSpacing, GSpacing,
170
                             GDALRasterIOExtraArg *psExtraArg) override;
171
};
172

173
/************************************************************************/
174
/* ==================================================================== */
175
/*                     OGCAPITilesWrapperBand                           */
176
/* ==================================================================== */
177
/************************************************************************/
178

179
class OGCAPITilesWrapperBand final : public GDALRasterBand
180
{
181
  public:
182
    OGCAPITilesWrapperBand(OGCAPIDataset *poDS, int nBand);
183

184
    virtual GDALRasterBand *GetOverview(int nLevel) override;
185
    virtual int GetOverviewCount() override;
186
    virtual GDALColorInterp GetColorInterpretation() override;
187

188
  protected:
189
    virtual CPLErr IReadBlock(int nBlockXOff, int nBlockYOff,
190
                              void *pImage) override;
191
    virtual CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
192
                             GDALDataType, GSpacing, GSpacing,
193
                             GDALRasterIOExtraArg *psExtraArg) override;
194
};
195

196
/************************************************************************/
197
/* ==================================================================== */
198
/*                           OGCAPITiledLayer                           */
199
/* ==================================================================== */
200
/************************************************************************/
201

202
class OGCAPITiledLayer;
203

204
class OGCAPITiledLayerFeatureDefn final : public OGRFeatureDefn
205
{
206
    OGCAPITiledLayer *m_poLayer = nullptr;
207

208
    CPL_DISALLOW_COPY_ASSIGN(OGCAPITiledLayerFeatureDefn)
209

210
  public:
211
    OGCAPITiledLayerFeatureDefn(OGCAPITiledLayer *poLayer, const char *pszName)
35✔
212
        : OGRFeatureDefn(pszName), m_poLayer(poLayer)
35✔
213
    {
214
    }
35✔
215

216
    int GetFieldCount() const override;
217

218
    void InvalidateLayer()
35✔
219
    {
220
        m_poLayer = nullptr;
35✔
221
    }
35✔
222
};
223

224
class OGCAPITiledLayer final
225
    : public OGRLayer,
226
      public OGRGetNextFeatureThroughRaw<OGCAPITiledLayer>
227
{
228
    OGCAPIDataset *m_poDS = nullptr;
229
    bool m_bFeatureDefnEstablished = false;
230
    bool m_bEstablishFieldsCalled =
231
        false;  // prevent recursion in EstablishFields()
232
    OGCAPITiledLayerFeatureDefn *m_poFeatureDefn = nullptr;
233
    OGREnvelope m_sEnvelope{};
234
    std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
235
    OGRLayer *m_poUnderlyingLayer = nullptr;
236
    int m_nCurY = 0;
237
    int m_nCurX = 0;
238

239
    CPLString m_osTileURL{};
240
    bool m_bIsMVT = false;
241

242
    const gdal::TileMatrixSet::TileMatrix m_oTileMatrix{};
243
    bool m_bInvertAxis = false;
244

245
    // absolute bounds
246
    int m_nMinX = 0;
247
    int m_nMaxX = 0;
248
    int m_nMinY = 0;
249
    int m_nMaxY = 0;
250

251
    // depends on spatial filter
252
    int m_nCurMinX = 0;
253
    int m_nCurMaxX = 0;
254
    int m_nCurMinY = 0;
255
    int m_nCurMaxY = 0;
256

257
    int GetCoalesceFactorForRow(int nRow) const;
258
    bool IncrementTileIndices();
259
    OGRFeature *GetNextRawFeature();
260
    GDALDataset *OpenTile(int nX, int nY, bool &bEmptyContent);
261
    void FinalizeFeatureDefnWithLayer(OGRLayer *poUnderlyingLayer);
262
    OGRFeature *BuildFeature(OGRFeature *poSrcFeature, int nX, int nY);
263

264
    CPL_DISALLOW_COPY_ASSIGN(OGCAPITiledLayer)
265

266
  protected:
267
    friend class OGCAPITiledLayerFeatureDefn;
268
    void EstablishFields();
269

270
  public:
271
    OGCAPITiledLayer(OGCAPIDataset *poDS, bool bInvertAxis,
272
                     const CPLString &osTileURL, bool bIsMVT,
273
                     const gdal::TileMatrixSet::TileMatrix &tileMatrix,
274
                     OGRwkbGeometryType eGeomType);
275
    ~OGCAPITiledLayer();
276

277
    void SetExtent(double dfXMin, double dfYMin, double dfXMax, double dfYMax);
278
    void SetFields(const std::vector<std::unique_ptr<OGRFieldDefn>> &apoFields);
279
    void SetMinMaxXY(int minCol, int minRow, int maxCol, int maxRow);
280

281
    void ResetReading() override;
282

283
    OGRFeatureDefn *GetLayerDefn() override
×
284
    {
285
        return m_poFeatureDefn;
×
286
    }
287

288
    const char *GetName() override
15✔
289
    {
290
        return m_poFeatureDefn->GetName();
15✔
291
    }
292

293
    OGRwkbGeometryType GetGeomType() override
×
294
    {
295
        return m_poFeatureDefn->GetGeomType();
×
296
    }
297
    DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGCAPITiledLayer)
5✔
298

299
    GIntBig GetFeatureCount(int /* bForce */) override
×
300
    {
301
        return -1;
×
302
    }
303

304
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
305
                      bool bForce) override;
306

307
    OGRErr ISetSpatialFilter(int iGeomField,
308
                             const OGRGeometry *poGeom) override;
309

310
    OGRFeature *GetFeature(GIntBig nFID) override;
311
    int TestCapability(const char *) override;
312
};
313

314
/************************************************************************/
315
/*                            GetFieldCount()                           */
316
/************************************************************************/
317

318
int OGCAPITiledLayerFeatureDefn::GetFieldCount() const
110✔
319
{
320
    if (m_poLayer)
110✔
321
    {
322
        m_poLayer->EstablishFields();
110✔
323
    }
324
    return OGRFeatureDefn::GetFieldCount();
110✔
325
}
326

327
/************************************************************************/
328
/*                           ~OGCAPIDataset()                           */
329
/************************************************************************/
330

331
OGCAPIDataset::~OGCAPIDataset()
70✔
332
{
333
    if (m_bMustCleanPersistent)
35✔
334
    {
335
        char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
35✔
336
                                              CPLSPrintf("OGCAPI:%p", this));
337
        CPLHTTPDestroyResult(CPLHTTPFetch(m_osRootURL, papszOptions));
35✔
338
        CSLDestroy(papszOptions);
35✔
339
    }
340

341
    OGCAPIDataset::CloseDependentDatasets();
35✔
342
}
70✔
343

344
/************************************************************************/
345
/*                        CloseDependentDatasets()                      */
346
/************************************************************************/
347

348
int OGCAPIDataset::CloseDependentDatasets()
35✔
349
{
350
    if (m_apoDatasetsElementary.empty())
35✔
351
        return false;
35✔
352

353
    // in this order
354
    m_apoDatasetsCropped.clear();
×
355
    m_apoDatasetsAssembled.clear();
×
356
    m_apoDatasetsElementary.clear();
×
357
    return true;
×
358
}
359

360
/************************************************************************/
361
/*                          GetGeoTransform()                           */
362
/************************************************************************/
363

364
CPLErr OGCAPIDataset::GetGeoTransform(GDALGeoTransform &gt) const
7✔
365
{
366
    gt = m_gt;
7✔
367
    return CE_None;
7✔
368
}
369

370
/************************************************************************/
371
/*                           GetSpatialRef()                            */
372
/************************************************************************/
373

374
const OGRSpatialReference *OGCAPIDataset::GetSpatialRef() const
3✔
375
{
376
    return !m_oSRS.IsEmpty() ? &m_oSRS : nullptr;
3✔
377
}
378

379
/************************************************************************/
380
/*                          CheckContentType()                          */
381
/************************************************************************/
382

383
// We may ask for "application/openapi+json;version=3.0"
384
// and the server returns "application/openapi+json; charset=utf-8; version=3.0"
385
static bool CheckContentType(const char *pszGotContentType,
93✔
386
                             const char *pszExpectedContentType)
387
{
388
    CPLStringList aosGotTokens(CSLTokenizeString2(pszGotContentType, "; ", 0));
186✔
389
    CPLStringList aosExpectedTokens(
390
        CSLTokenizeString2(pszExpectedContentType, "; ", 0));
186✔
391
    for (int i = 0; i < aosExpectedTokens.size(); i++)
186✔
392
    {
393
        bool bFound = false;
93✔
394
        for (int j = 0; j < aosGotTokens.size(); j++)
93✔
395
        {
396
            if (EQUAL(aosExpectedTokens[i], aosGotTokens[j]))
93✔
397
            {
398
                bFound = true;
93✔
399
                break;
93✔
400
            }
401
        }
402
        if (!bFound)
93✔
403
            return false;
×
404
    }
405
    return true;
93✔
406
}
407

408
/************************************************************************/
409
/*                              Download()                              */
410
/************************************************************************/
411

412
bool OGCAPIDataset::Download(const CPLString &osURL, const char *pszPostContent,
122✔
413
                             const char *pszAccept, CPLString &osResult,
414
                             CPLString &osContentType, bool bEmptyContentOK,
415
                             CPLStringList *paosHeaders)
416
{
417
    char **papszOptions = nullptr;
122✔
418
    CPLString osHeaders;
244✔
419
    if (pszAccept)
122✔
420
    {
421
        osHeaders += "Accept: ";
101✔
422
        osHeaders += pszAccept;
101✔
423
    }
424
    if (pszPostContent)
122✔
425
    {
426
        if (!osHeaders.empty())
×
427
        {
428
            osHeaders += "\r\n";
×
429
        }
430
        osHeaders += "Content-Type: application/json";
×
431
    }
432
    if (!osHeaders.empty())
122✔
433
    {
434
        papszOptions =
435
            CSLSetNameValue(papszOptions, "HEADERS", osHeaders.c_str());
101✔
436
    }
437
    if (!m_osUserPwd.empty())
122✔
438
    {
439
        papszOptions =
440
            CSLSetNameValue(papszOptions, "USERPWD", m_osUserPwd.c_str());
×
441
    }
442
    m_bMustCleanPersistent = true;
122✔
443
    papszOptions =
444
        CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=OGCAPI:%p", this));
122✔
445
    CPLString osURLWithQueryParameters(osURL);
244✔
446
    if (!m_osUserQueryParams.empty() &&
×
447
        osURL.find('?' + m_osUserQueryParams) == std::string::npos &&
244✔
448
        osURL.find('&' + m_osUserQueryParams) == std::string::npos)
122✔
449
    {
450
        if (osURL.find('?') == std::string::npos)
×
451
        {
452
            osURLWithQueryParameters += '?';
×
453
        }
454
        else
455
        {
456
            osURLWithQueryParameters += '&';
×
457
        }
458
        osURLWithQueryParameters += m_osUserQueryParams;
×
459
    }
460
    if (pszPostContent)
122✔
461
    {
462
        papszOptions =
463
            CSLSetNameValue(papszOptions, "POSTFIELDS", pszPostContent);
×
464
    }
465
    CPLHTTPResult *psResult =
466
        CPLHTTPFetch(osURLWithQueryParameters, papszOptions);
122✔
467
    CSLDestroy(papszOptions);
122✔
468
    if (!psResult)
122✔
469
        return false;
×
470

471
    if (paosHeaders)
122✔
472
    {
473
        *paosHeaders = CSLDuplicate(psResult->papszHeaders);
×
474
    }
475

476
    if (psResult->pszErrBuf != nullptr)
122✔
477
    {
478
        std::string osErrorMsg(psResult->pszErrBuf);
8✔
479
        const char *pszData =
8✔
480
            reinterpret_cast<const char *>(psResult->pabyData);
481
        if (pszData)
8✔
482
        {
483
            osErrorMsg += ", ";
8✔
484
            osErrorMsg.append(pszData, CPLStrnlen(pszData, 1000));
8✔
485
        }
486
        CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
8✔
487
        CPLHTTPDestroyResult(psResult);
8✔
488
        return false;
8✔
489
    }
490

491
    if (psResult->pszContentType)
114✔
492
        osContentType = psResult->pszContentType;
114✔
493

494
    if (pszAccept != nullptr)
114✔
495
    {
496
        bool bFoundExpectedContentType = false;
93✔
497
        if (strstr(pszAccept, "xml") && psResult->pszContentType != nullptr &&
93✔
498
            (CheckContentType(psResult->pszContentType, MEDIA_TYPE_TEXT_XML) ||
×
499
             CheckContentType(psResult->pszContentType,
×
500
                              MEDIA_TYPE_APPLICATION_XML)))
501
        {
502
            bFoundExpectedContentType = true;
×
503
        }
504

505
        if (strstr(pszAccept, MEDIA_TYPE_JSON_SCHEMA) &&
186✔
506
            psResult->pszContentType != nullptr &&
93✔
507
            (CheckContentType(psResult->pszContentType, MEDIA_TYPE_JSON) ||
×
508
             CheckContentType(psResult->pszContentType,
×
509
                              MEDIA_TYPE_JSON_SCHEMA)))
510
        {
511
            bFoundExpectedContentType = true;
×
512
        }
513

514
        for (const char *pszMediaType : {
×
515
                 MEDIA_TYPE_JSON,
516
                 MEDIA_TYPE_GEOJSON,
517
                 MEDIA_TYPE_OAPI_3_0,
518
             })
93✔
519
        {
520
            if (strstr(pszAccept, pszMediaType) &&
279✔
521
                psResult->pszContentType != nullptr &&
186✔
522
                CheckContentType(psResult->pszContentType, pszMediaType))
93✔
523
            {
524
                bFoundExpectedContentType = true;
93✔
525
                break;
93✔
526
            }
527
        }
528

529
        if (!bFoundExpectedContentType)
93✔
530
        {
531
            CPLError(CE_Failure, CPLE_AppDefined, "Unexpected Content-Type: %s",
×
532
                     psResult->pszContentType ? psResult->pszContentType
×
533
                                              : "(null)");
534
            CPLHTTPDestroyResult(psResult);
×
535
            return false;
×
536
        }
537
    }
538

539
    if (psResult->pabyData == nullptr)
114✔
540
    {
541
        osResult.clear();
15✔
542
        if (!bEmptyContentOK)
15✔
543
        {
544
            CPLError(CE_Failure, CPLE_AppDefined,
×
545
                     "Empty content returned by server");
546
            CPLHTTPDestroyResult(psResult);
×
547
            return false;
×
548
        }
549
    }
550
    else
551
    {
552
        osResult.assign(reinterpret_cast<const char *>(psResult->pabyData),
99✔
553
                        psResult->nDataLen);
99✔
554
#ifdef DEBUG_VERBOSE
555
        CPLDebug("OGCAPI", "%s", osResult.c_str());
556
#endif
557
    }
558
    CPLHTTPDestroyResult(psResult);
114✔
559
    return true;
114✔
560
}
561

562
/************************************************************************/
563
/*                           DownloadJSon()                             */
564
/************************************************************************/
565

566
bool OGCAPIDataset::DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
101✔
567
                                 const char *pszPostContent,
568
                                 const char *pszAccept,
569
                                 CPLStringList *paosHeaders)
570
{
571
    CPLString osResult;
202✔
572
    CPLString osContentType;
202✔
573
    if (!Download(osURL, pszPostContent, pszAccept, osResult, osContentType,
101✔
574
                  false, paosHeaders))
575
        return false;
8✔
576
    return oDoc.LoadMemory(osResult);
93✔
577
}
578

579
/************************************************************************/
580
/*                            OpenTile()                                */
581
/************************************************************************/
582

583
std::unique_ptr<GDALDataset>
584
OGCAPIDataset::OpenTile(const CPLString &osURLPattern, int nMatrix, int nColumn,
21✔
585
                        int nRow, bool &bEmptyContent,
586
                        unsigned int nOpenTileFlags, const CPLString &osPrefix,
587
                        const char *const *papszOpenTileOptions)
588
{
589
    CPLString osURL(osURLPattern);
42✔
590
    osURL.replaceAll("{tileMatrix}", CPLSPrintf("%d", nMatrix));
21✔
591
    osURL.replaceAll("{tileCol}", CPLSPrintf("%d", nColumn));
21✔
592
    osURL.replaceAll("{tileRow}", CPLSPrintf("%d", nRow));
21✔
593

594
    CPLString osContentType;
42✔
595
    if (!this->Download(osURL, nullptr, nullptr, m_osTileData, osContentType,
21✔
596
                        true, nullptr))
597
    {
598
        return nullptr;
×
599
    }
600

601
    bEmptyContent = m_osTileData.empty();
21✔
602
    if (bEmptyContent)
21✔
603
        return nullptr;
15✔
604

605
    const CPLString osTempFile(VSIMemGenerateHiddenFilename("ogcapi"));
12✔
606
    VSIFCloseL(VSIFileFromMemBuffer(osTempFile.c_str(),
6✔
607
                                    reinterpret_cast<GByte *>(&m_osTileData[0]),
6✔
608
                                    m_osTileData.size(), false));
6✔
609

610
    GDALDataset *result = nullptr;
6✔
611

612
    if (osPrefix.empty())
6✔
613
        result = GDALDataset::Open(osTempFile.c_str(), nOpenTileFlags, nullptr,
3✔
614
                                   papszOpenTileOptions);
615
    else
616
        result =
617
            GDALDataset::Open((osPrefix + ":" + osTempFile).c_str(),
3✔
618
                              nOpenTileFlags, nullptr, papszOpenTileOptions);
619

620
    VSIUnlink(osTempFile);
6✔
621

622
    return std::unique_ptr<GDALDataset>(result);
6✔
623
}
624

625
/************************************************************************/
626
/*                            Identify()                                */
627
/************************************************************************/
628

629
int OGCAPIDataset::Identify(GDALOpenInfo *poOpenInfo)
66,484✔
630
{
631
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:"))
66,484✔
632
        return TRUE;
58✔
633
    if (poOpenInfo->IsExtensionEqualToCI("moaw"))
66,426✔
634
        return TRUE;
×
635
    if (poOpenInfo->IsSingleAllowedDriver("OGCAPI"))
66,426✔
636
    {
637
        return TRUE;
12✔
638
    }
639
    return FALSE;
66,413✔
640
}
641

642
/************************************************************************/
643
/*                            BuildURL()                                */
644
/************************************************************************/
645

646
CPLString OGCAPIDataset::BuildURL(const std::string &href) const
7,783✔
647
{
648
    if (!href.empty() && href[0] == '/')
7,783✔
649
        return m_osRootURL + href;
×
650
    return href;
7,783✔
651
}
652

653
/************************************************************************/
654
/*                         SetRootURLFromURL()                          */
655
/************************************************************************/
656

657
void OGCAPIDataset::SetRootURLFromURL(const std::string &osURL)
27✔
658
{
659
    const char *pszStr = osURL.c_str();
27✔
660
    const char *pszPtr = pszStr;
27✔
661
    if (STARTS_WITH(pszPtr, "http://"))
27✔
662
        pszPtr += strlen("http://");
27✔
663
    else if (STARTS_WITH(pszPtr, "https://"))
×
664
        pszPtr += strlen("https://");
×
665
    pszPtr = strchr(pszPtr, '/');
27✔
666
    if (pszPtr)
27✔
667
        m_osRootURL.assign(pszStr, pszPtr - pszStr);
27✔
668
}
27✔
669

670
/************************************************************************/
671
/*                          FigureBands()                               */
672
/************************************************************************/
673

674
int OGCAPIDataset::FigureBands(const std::string &osContentType,
9✔
675
                               const CPLString &osImageURL)
676
{
677
    int result = 0;
9✔
678

679
    if (osContentType == "image/png")
9✔
680
    {
681
        result = 4;
6✔
682
    }
683
    else if (osContentType == "image/jpeg")
3✔
684
    {
685
        result = 3;
2✔
686
    }
687
    else
688
    {
689
        // Since we don't know the format download a tile and find out
690
        bool bEmptyContent = false;
1✔
691
        std::unique_ptr<GDALDataset> dataset =
692
            OpenTile(osImageURL, 0, 0, 0, bEmptyContent, GDAL_OF_RASTER);
1✔
693

694
        // Return the bands from the image, if we didn't get an image then assume 3.
695
        result = dataset ? static_cast<int>(dataset->GetBands().size()) : 3;
1✔
696
    }
697

698
    return result;
9✔
699
}
700

701
/************************************************************************/
702
/*                           InitFromFile()                             */
703
/************************************************************************/
704

705
bool OGCAPIDataset::InitFromFile(GDALOpenInfo *poOpenInfo)
×
706
{
707
    CPLJSONDocument oDoc;
×
708
    if (!oDoc.Load(poOpenInfo->pszFilename))
×
709
        return false;
×
710
    auto oProcess = oDoc.GetRoot()["process"];
×
711
    if (oProcess.GetType() != CPLJSONObject::Type::String)
×
712
    {
713
        CPLError(CE_Failure, CPLE_AppDefined,
×
714
                 "Cannot find 'process' key in .moaw file");
715
        return false;
×
716
    }
717

718
    const CPLString osURLProcess(oProcess.ToString());
×
719
    SetRootURLFromURL(osURLProcess);
×
720

721
    GByte *pabyContent = nullptr;
×
722
    vsi_l_offset nSize = 0;
×
723
    if (!VSIIngestFile(poOpenInfo->fpL, nullptr, &pabyContent, &nSize,
×
724
                       1024 * 1024))
725
        return false;
×
726
    CPLString osPostContent(reinterpret_cast<const char *>(pabyContent));
×
727
    CPLFree(pabyContent);
×
728
    if (!DownloadJSon(osURLProcess.c_str(), oDoc, osPostContent.c_str()))
×
729
        return false;
×
730

731
    return InitFromCollection(poOpenInfo, oDoc);
×
732
}
733

734
/************************************************************************/
735
/*                        ProcessScale()                          */
736
/************************************************************************/
737

738
bool OGCAPIDataset::ProcessScale(const CPLJSONObject &oScaleDenominator,
17✔
739
                                 const double dfXMin, const double dfYMin,
740
                                 const double dfXMax, const double dfYMax)
741

742
{
743
    double dfRes = 1e-8;  // arbitrary
17✔
744
    if (oScaleDenominator.IsValid())
17✔
745
    {
746
        const double dfScaleDenominator = oScaleDenominator.ToDouble();
×
747
        constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
×
748
        dfRes = dfScaleDenominator / ((HALF_CIRCUMFERENCE / 180) / 0.28e-3);
×
749
    }
750
    if (dfRes == 0.0)
17✔
751
        return false;
×
752

753
    double dfXSize = (dfXMax - dfXMin) / dfRes;
17✔
754
    double dfYSize = (dfYMax - dfYMin) / dfRes;
17✔
755
    while (dfXSize > INT_MAX || dfYSize > INT_MAX)
97✔
756
    {
757
        dfXSize /= 2;
80✔
758
        dfYSize /= 2;
80✔
759
    }
760

761
    nRasterXSize = std::max(1, static_cast<int>(0.5 + dfXSize));
17✔
762
    nRasterYSize = std::max(1, static_cast<int>(0.5 + dfYSize));
17✔
763
    m_gt[0] = dfXMin;
17✔
764
    m_gt[1] = (dfXMax - dfXMin) / nRasterXSize;
17✔
765
    m_gt[3] = dfYMax;
17✔
766
    m_gt[5] = -(dfYMax - dfYMin) / nRasterYSize;
17✔
767

768
    return true;
17✔
769
}
770

771
/************************************************************************/
772
/*                        InitFromCollection()                          */
773
/************************************************************************/
774

775
bool OGCAPIDataset::InitFromCollection(GDALOpenInfo *poOpenInfo,
17✔
776
                                       CPLJSONDocument &oDoc)
777
{
778
    const CPLJSONObject oRoot = oDoc.GetRoot();
34✔
779
    auto osTitle = oRoot.GetString("title");
51✔
780
    if (!osTitle.empty())
17✔
781
    {
782
        SetMetadataItem("TITLE", osTitle.c_str());
17✔
783
    }
784

785
    auto oLinks = oRoot.GetArray("links");
51✔
786
    if (!oLinks.IsValid())
17✔
787
    {
788
        CPLError(CE_Failure, CPLE_AppDefined, "Missing links");
×
789
        return false;
×
790
    }
791
    auto oBboxes = oRoot["extent"]["spatial"]["bbox"].ToArray();
51✔
792
    if (oBboxes.Size() != 1)
17✔
793
    {
794
        CPLError(CE_Failure, CPLE_AppDefined, "Missing bbox");
×
795
        return false;
×
796
    }
797
    auto oBbox = oBboxes[0].ToArray();
34✔
798
    if (oBbox.Size() != 4)
17✔
799
    {
800
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid bbox");
×
801
        return false;
×
802
    }
803
    const bool bBBOXIsInCRS84 =
804
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MINX") == nullptr;
17✔
805
    const double dfXMin =
806
        CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MINX",
17✔
807
                                     CPLSPrintf("%.17g", oBbox[0].ToDouble())));
808
    const double dfYMin =
809
        CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MINY",
17✔
810
                                     CPLSPrintf("%.17g", oBbox[1].ToDouble())));
811
    const double dfXMax =
812
        CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAXX",
17✔
813
                                     CPLSPrintf("%.17g", oBbox[2].ToDouble())));
814
    const double dfYMax =
815
        CPLAtof(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAXY",
17✔
816
                                     CPLSPrintf("%.17g", oBbox[3].ToDouble())));
817

818
    auto oScaleDenominator = oRoot["scaleDenominator"];
51✔
819

820
    if (!ProcessScale(oScaleDenominator, dfXMin, dfYMin, dfXMax, dfYMax))
17✔
821
        return false;
×
822

823
    bool bFoundMap = false;
17✔
824

825
    CPLString osTilesetsMapURL;
34✔
826
    bool bTilesetsMapURLJson = false;
17✔
827

828
    CPLString osTilesetsVectorURL;
34✔
829
    bool bTilesetsVectorURLJson = false;
17✔
830

831
    CPLString osCoverageURL;
34✔
832
    bool bCoverageGeotiff = false;
17✔
833

834
    CPLString osItemsURL;
34✔
835
    bool bItemsJson = false;
17✔
836

837
    CPLString osSelfURL;
34✔
838
    bool bSelfJson = false;
17✔
839

840
    for (const auto &oLink : oLinks)
530✔
841
    {
842
        const auto osRel = oLink.GetString("rel");
1,539✔
843
        const auto osType = oLink.GetString("type");
1,539✔
844
        if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/map" ||
975✔
845
             osRel == "[ogc-rel:map]") &&
1,060✔
846
            (osType == "image/png" || osType == "image/jpeg"))
85✔
847
        {
848
            bFoundMap = true;
34✔
849
        }
850
        else if (!bTilesetsMapURLJson &&
1,258✔
851
                 (osRel ==
399✔
852
                      "http://www.opengis.net/def/rel/ogc/1.0/tilesets-map" ||
380✔
853
                  osRel == "[ogc-rel:tilesets-map]"))
380✔
854
        {
855
            if (osType == MEDIA_TYPE_JSON)
19✔
856
            {
857
                bTilesetsMapURLJson = true;
16✔
858
                osTilesetsMapURL = BuildURL(oLink["href"].ToString());
16✔
859
            }
860
            else if (osType.empty())
3✔
861
            {
862
                osTilesetsMapURL = BuildURL(oLink["href"].ToString());
1✔
863
            }
864
        }
865
        else if (!bTilesetsVectorURLJson &&
1,241✔
866
                 (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
396✔
867
                           "tilesets-vector" ||
385✔
868
                  osRel == "[ogc-rel:tilesets-vector]"))
385✔
869
        {
870
            if (osType == MEDIA_TYPE_JSON)
11✔
871
            {
872
                bTilesetsVectorURLJson = true;
8✔
873
                osTilesetsVectorURL = BuildURL(oLink["href"].ToString());
8✔
874
            }
875
            else if (osType.empty())
3✔
876
            {
877
                osTilesetsVectorURL = BuildURL(oLink["href"].ToString());
1✔
878
            }
879
        }
880
        else if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/coverage" ||
882✔
881
                  osRel == "[ogc-rel:coverage]") &&
906✔
882
                 (osType == "image/tiff; application=geotiff" ||
24✔
883
                  osType == "application/x-geotiff"))
8✔
884
        {
885
            if (!bCoverageGeotiff)
8✔
886
            {
887
                osCoverageURL = BuildURL(oLink["href"].ToString());
8✔
888
                bCoverageGeotiff = true;
8✔
889
            }
890
        }
891
        else if ((osRel == "http://www.opengis.net/def/rel/ogc/1.0/coverage" ||
874✔
892
                  osRel == "[ogc-rel:coverage]") &&
882✔
893
                 osType.empty())
8✔
894
        {
895
            osCoverageURL = BuildURL(oLink["href"].ToString());
×
896
        }
897
        else if (!bItemsJson && osRel == "items")
441✔
898
        {
899
            if (osType == MEDIA_TYPE_GEOJSON || osType == MEDIA_TYPE_JSON)
9✔
900
            {
901
                bItemsJson = true;
9✔
902
                osItemsURL = BuildURL(oLink["href"].ToString());
9✔
903
            }
904
            else if (osType.empty())
×
905
            {
906
                osItemsURL = BuildURL(oLink["href"].ToString());
×
907
            }
908
        }
909
        else if (!bSelfJson && osRel == "self")
432✔
910
        {
911
            if (osType == "application/json")
17✔
912
            {
913
                bSelfJson = true;
16✔
914
                osSelfURL = BuildURL(oLink["href"].ToString());
16✔
915
            }
916
            else if (osType.empty())
1✔
917
            {
918
                osSelfURL = BuildURL(oLink["href"].ToString());
1✔
919
            }
920
        }
921
    }
922

923
    if (!bFoundMap && osTilesetsMapURL.empty() && osTilesetsVectorURL.empty() &&
×
924
        osCoverageURL.empty() && osSelfURL.empty() && osItemsURL.empty())
17✔
925
    {
926
        CPLError(CE_Failure, CPLE_AppDefined,
×
927
                 "Missing map, tilesets, coverage or items relation in links");
928
        return false;
×
929
    }
930

931
    const char *pszAPI =
932
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "API", "AUTO");
17✔
933
    if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "COVERAGE")) &&
18✔
934
        !osCoverageURL.empty())
1✔
935
    {
936
        return InitWithCoverageAPI(poOpenInfo, osCoverageURL, dfXMin, dfYMin,
1✔
937
                                   dfXMax, dfYMax, oDoc.GetRoot());
2✔
938
    }
939
    else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "TILES")) &&
29✔
940
             (!osTilesetsMapURL.empty() || !osTilesetsVectorURL.empty()))
13✔
941
    {
942
        bool bRet = false;
13✔
943
        if (!osTilesetsMapURL.empty())
13✔
944
            bRet = InitWithTilesAPI(poOpenInfo, osTilesetsMapURL, true, dfXMin,
13✔
945
                                    dfYMin, dfXMax, dfYMax, bBBOXIsInCRS84,
946
                                    oDoc.GetRoot());
26✔
947
        if (!bRet && !osTilesetsVectorURL.empty())
13✔
948
            bRet = InitWithTilesAPI(poOpenInfo, osTilesetsVectorURL, false,
5✔
949
                                    dfXMin, dfYMin, dfXMax, dfYMax,
950
                                    bBBOXIsInCRS84, oDoc.GetRoot());
10✔
951
        return bRet;
13✔
952
    }
953
    else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "MAP")) && bFoundMap)
3✔
954
    {
955
        return InitWithMapAPI(poOpenInfo, oRoot, dfXMin, dfYMin, dfXMax,
1✔
956
                              dfYMax);
1✔
957
    }
958
    else if ((EQUAL(pszAPI, "AUTO") || EQUAL(pszAPI, "ITEMS")) &&
2✔
959
             !osSelfURL.empty() && !osItemsURL.empty() &&
6✔
960
             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
2✔
961
    {
962
        m_poOAPIFDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
4✔
963
            ("OAPIF_COLLECTION:" + osSelfURL).c_str(), GDAL_OF_VECTOR));
6✔
964
        if (m_poOAPIFDS)
2✔
965
            return true;
2✔
966
    }
967

968
    CPLError(CE_Failure, CPLE_AppDefined, "API %s requested, but not available",
×
969
             pszAPI);
970
    return false;
×
971
}
972

973
/************************************************************************/
974
/*                               InitFromURL()                          */
975
/************************************************************************/
976

977
bool OGCAPIDataset::InitFromURL(GDALOpenInfo *poOpenInfo)
35✔
978
{
979
    const char *pszInitialURL =
6✔
980
        STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:")
35✔
981
            ? poOpenInfo->pszFilename + strlen("OGCAPI:")
29✔
982
            : poOpenInfo->pszFilename;
983
    CPLJSONDocument oDoc;
70✔
984
    CPLString osURL(pszInitialURL);
70✔
985
    if (!DownloadJSon(osURL, oDoc))
35✔
986
        return false;
8✔
987

988
    SetRootURLFromURL(osURL);
27✔
989

990
    auto oCollections = oDoc.GetRoot().GetArray("collections");
81✔
991
    if (!oCollections.IsValid())
27✔
992
    {
993
        if (!oDoc.GetRoot().GetArray("extent").IsValid())
27✔
994
        {
995
            // If there is no "colletions" or "extent" member, then it is
996
            // perhaps a landing page
997
            const auto oLinks = oDoc.GetRoot().GetArray("links");
54✔
998
            osURL.clear();
27✔
999
            for (const auto &oLink : oLinks)
628✔
1000
            {
1001
                if (oLink["rel"].ToString() == "data" &&
1,214✔
1002
                    oLink["type"].ToString() == MEDIA_TYPE_JSON)
613✔
1003
                {
1004
                    osURL = BuildURL(oLink["href"].ToString());
9✔
1005
                    break;
9✔
1006
                }
1007
                else if (oLink["rel"].ToString() == "data" &&
1,187✔
1008
                         !oLink.GetObj("type").IsValid())
595✔
1009
                {
1010
                    osURL = BuildURL(oLink["href"].ToString());
1✔
1011
                }
1012
            }
1013
            if (!osURL.empty())
27✔
1014
            {
1015
                if (!DownloadJSon(osURL, oDoc))
10✔
1016
                    return false;
×
1017
                oCollections = oDoc.GetRoot().GetArray("collections");
10✔
1018
            }
1019
        }
1020

1021
        if (!oCollections.IsValid())
27✔
1022
        {
1023
            // This is hopefully a /collections/{id} response
1024
            return InitFromCollection(poOpenInfo, oDoc);
17✔
1025
        }
1026
    }
1027

1028
    // This is a /collections response
1029
    CPLStringList aosSubdatasets;
10✔
1030
    for (const auto &oCollection : oCollections)
7,500✔
1031
    {
1032
        const auto osTitle = oCollection.GetString("title");
14,980✔
1033
        const auto osLayerDataType = oCollection.GetString("layerDataType");
14,980✔
1034
        // CPLDebug("OGCAPI", "%s: %s", osTitle.c_str(),
1035
        // osLayerDataType.c_str());
1036
        if (!osLayerDataType.empty() &&
7,490✔
1037
            (EQUAL(osLayerDataType.c_str(), "Raster") ||
×
1038
             EQUAL(osLayerDataType.c_str(), "Coverage")) &&
7,490✔
1039
            (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
×
1040
        {
1041
            continue;
×
1042
        }
1043
        if (!osLayerDataType.empty() &&
7,490✔
1044
            EQUAL(osLayerDataType.c_str(), "Vector") &&
7,490✔
1045
            (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
×
1046
        {
1047
            continue;
×
1048
        }
1049
        osURL.clear();
7,490✔
1050
        const auto oLinks = oCollection.GetArray("links");
14,980✔
1051
        for (const auto &oLink : oLinks)
38,649✔
1052
        {
1053
            if (oLink["rel"].ToString() == "self" &&
69,808✔
1054
                oLink["type"].ToString() == "application/json")
38,649✔
1055
            {
1056
                osURL = BuildURL(oLink["href"].ToString());
6,741✔
1057
                break;
6,741✔
1058
            }
1059
            else if (oLink["rel"].ToString() == "self" &&
49,585✔
1060
                     oLink.GetString("type").empty())
25,167✔
1061
            {
1062
                osURL = BuildURL(oLink["href"].ToString());
749✔
1063
            }
1064
        }
1065
        if (osURL.empty())
7,490✔
1066
        {
1067
            continue;
×
1068
        }
1069
        const int nIdx = 1 + aosSubdatasets.size() / 2;
7,490✔
1070
        aosSubdatasets.AddNameValue(CPLSPrintf("SUBDATASET_%d_NAME", nIdx),
1071
                                    CPLSPrintf("OGCAPI:%s", osURL.c_str()));
7,490✔
1072
        aosSubdatasets.AddNameValue(
1073
            CPLSPrintf("SUBDATASET_%d_DESC", nIdx),
1074
            CPLSPrintf("Collection %s", osTitle.c_str()));
7,490✔
1075
    }
1076
    SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
10✔
1077

1078
    return true;
10✔
1079
}
1080

1081
/************************************************************************/
1082
/*                          SelectImageURL()                            */
1083
/************************************************************************/
1084

1085
static const std::pair<std::string, std::string>
1086
SelectImageURL(const char *const *papszOptionOptions,
19✔
1087
               std::map<std::string, std::string> &oMapItemUrls)
1088
{
1089
    // Map IMAGE_FORMATS to their content types. Would be nice if this was
1090
    // globally defined someplace
1091
    const std::map<std::string, std::vector<std::string>>
1092
        oFormatContentTypeMap = {
1093
            {"AUTO",
1094
             {"image/png", "image/jpeg", "image/tiff; application=geotiff"}},
1095
            {"PNG_PREFERRED",
1096
             {"image/png", "image/jpeg", "image/tiff; application=geotiff"}},
1097
            {"JPEG_PREFERRED",
1098
             {"image/jpeg", "image/png", "image/tiff; application=geotiff"}},
1099
            {"PNG", {"image/png"}},
1100
            {"JPEG", {"image/jpeg"}},
1101
            {"GEOTIFF", {"image/tiff; application=geotiff"}}};
532✔
1102

1103
    // Get the IMAGE_FORMAT
1104
    const std::string osFormat =
1105
        CSLFetchNameValueDef(papszOptionOptions, "IMAGE_FORMAT", "AUTO");
38✔
1106

1107
    // Get a list of content types we will search for in priority order based on IMAGE_FORMAT
1108
    auto iterFormat = oFormatContentTypeMap.find(osFormat);
19✔
1109
    if (iterFormat == oFormatContentTypeMap.end())
19✔
1110
    {
1111
        CPLError(CE_Failure, CPLE_AppDefined,
×
1112
                 "Unknown IMAGE_FORMAT specified: %s", osFormat.c_str());
1113
        return std::pair<std::string, CPLString>();
×
1114
    }
1115
    std::vector<std::string> oContentTypes = iterFormat->second;
38✔
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
    if (osFormat == "AUTO" || osFormat == "PNG_PREFERRED" ||
23✔
1121
        osFormat == "JPEG_PREFERRED")
4✔
1122
    {
1123
        std::transform(oMapItemUrls.begin(), oMapItemUrls.end(),
1124
                       std::back_inserter(oContentTypes),
1125
                       [](const auto &pair) -> const std::string &
44✔
1126
                       { return pair.first; });
60✔
1127
    }
1128

1129
    // Loop over each content type - return the first one we find
1130
    for (auto &oContentType : oContentTypes)
34✔
1131
    {
1132
        auto iterContentType = oMapItemUrls.find(oContentType);
29✔
1133
        if (iterContentType != oMapItemUrls.end())
29✔
1134
        {
1135
            return *iterContentType;
14✔
1136
        }
1137
    }
1138

1139
    if (osFormat != "AUTO")
5✔
1140
    {
1141
        CPLError(CE_Failure, CPLE_AppDefined,
×
1142
                 "Server does not support specified IMAGE_FORMAT: %s",
1143
                 osFormat.c_str());
1144
    }
1145
    return std::pair<std::string, CPLString>();
5✔
1146
}
1147

1148
/************************************************************************/
1149
/*                        SelectVectorFormatURL()                       */
1150
/************************************************************************/
1151

1152
static const CPLString
1153
SelectVectorFormatURL(const char *const *papszOptionOptions,
18✔
1154
                      const CPLString &osMVT_URL,
1155
                      const CPLString &osGEOJSON_URL)
1156
{
1157
    const char *pszFormat =
1158
        CSLFetchNameValueDef(papszOptionOptions, "VECTOR_FORMAT", "AUTO");
18✔
1159
    if (EQUAL(pszFormat, "AUTO") || EQUAL(pszFormat, "MVT_PREFERRED"))
18✔
1160
        return !osMVT_URL.empty() ? osMVT_URL : osGEOJSON_URL;
12✔
1161
    else if (EQUAL(pszFormat, "MVT"))
6✔
1162
        return osMVT_URL;
2✔
1163
    else if (EQUAL(pszFormat, "GEOJSON"))
4✔
1164
        return osGEOJSON_URL;
2✔
1165
    else if (EQUAL(pszFormat, "GEOJSON_PREFERRED"))
2✔
1166
        return !osGEOJSON_URL.empty() ? osGEOJSON_URL : osMVT_URL;
2✔
1167
    return CPLString();
×
1168
}
1169

1170
/************************************************************************/
1171
/*                          InitWithMapAPI()                            */
1172
/************************************************************************/
1173

1174
bool OGCAPIDataset::InitWithMapAPI(GDALOpenInfo *poOpenInfo,
1✔
1175
                                   const CPLJSONObject &oRoot, double dfXMin,
1176
                                   double dfYMin, double dfXMax, double dfYMax)
1177
{
1178
    auto oLinks = oRoot["links"].ToArray();
3✔
1179

1180
    // Key - mime type, Value url
1181
    std::map<std::string, std::string> oMapItemUrls;
2✔
1182

1183
    for (const auto &oLink : oLinks)
36✔
1184
    {
1185
        if (oLink["rel"].ToString() ==
70✔
1186
                "http://www.opengis.net/def/rel/ogc/1.0/map" &&
73✔
1187
            oLink["type"].IsValid())
38✔
1188
        {
1189
            oMapItemUrls[oLink["type"].ToString()] =
6✔
1190
                BuildURL(oLink["href"].ToString());
9✔
1191
        }
1192
        else
1193
        {
1194
            // For lack of additional information assume we are getting some bytes
1195
            oMapItemUrls["application/octet-stream"] =
64✔
1196
                BuildURL(oLink["href"].ToString());
96✔
1197
        }
1198
    }
1199

1200
    const std::pair<std::string, std::string> oContentUrlPair =
1201
        SelectImageURL(poOpenInfo->papszOpenOptions, oMapItemUrls);
2✔
1202
    const std::string osContentType = oContentUrlPair.first;
2✔
1203
    const std::string osImageURL = oContentUrlPair.second;
2✔
1204

1205
    if (osImageURL.empty())
1✔
1206
    {
1207
        CPLError(CE_Failure, CPLE_AppDefined,
×
1208
                 "Cannot find link to tileset items");
1209
        return false;
×
1210
    }
1211

1212
    int l_nBands = FigureBands(osContentType, osImageURL);
1✔
1213
    int nOverviewCount = 0;
1✔
1214
    int nLargestDim = std::max(nRasterXSize, nRasterYSize);
1✔
1215
    while (nLargestDim > 256)
24✔
1216
    {
1217
        nOverviewCount++;
23✔
1218
        nLargestDim /= 2;
23✔
1219
    }
1220

1221
    m_oSRS.importFromEPSG(4326);
1✔
1222
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1✔
1223

1224
    const bool bCache = CPLTestBool(
1✔
1225
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
1✔
1226
    const int nMaxConnections = atoi(
1✔
1227
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
1✔
1228
                             CPLGetConfigOption("GDAL_MAX_CONNECTIONS", "5")));
1229
    CPLString osWMS_XML;
2✔
1230
    char *pszEscapedURL = CPLEscapeString(osImageURL.c_str(), -1, CPLES_XML);
1✔
1231
    osWMS_XML.Printf("<GDAL_WMS>"
1232
                     "    <Service name=\"OGCAPIMaps\">"
1233
                     "        <ServerUrl>%s</ServerUrl>"
1234
                     "    </Service>"
1235
                     "    <DataWindow>"
1236
                     "        <UpperLeftX>%.17g</UpperLeftX>"
1237
                     "        <UpperLeftY>%.17g</UpperLeftY>"
1238
                     "        <LowerRightX>%.17g</LowerRightX>"
1239
                     "        <LowerRightY>%.17g</LowerRightY>"
1240
                     "        <SizeX>%d</SizeX>"
1241
                     "        <SizeY>%d</SizeY>"
1242
                     "    </DataWindow>"
1243
                     "    <OverviewCount>%d</OverviewCount>"
1244
                     "    <BlockSizeX>256</BlockSizeX>"
1245
                     "    <BlockSizeY>256</BlockSizeY>"
1246
                     "    <BandsCount>%d</BandsCount>"
1247
                     "    <MaxConnections>%d</MaxConnections>"
1248
                     "    %s"
1249
                     "</GDAL_WMS>",
1250
                     pszEscapedURL, dfXMin, dfYMax, dfXMax, dfYMin,
1251
                     nRasterXSize, nRasterYSize, nOverviewCount, l_nBands,
1252
                     nMaxConnections, bCache ? "<Cache />" : "");
1✔
1253
    CPLFree(pszEscapedURL);
1✔
1254
    CPLDebug("OGCAPI", "%s", osWMS_XML.c_str());
1✔
1255
    m_poWMSDS.reset(
1✔
1256
        GDALDataset::Open(osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
1257
    if (m_poWMSDS == nullptr)
1✔
1258
        return false;
×
1259

1260
    for (int i = 1; i <= m_poWMSDS->GetRasterCount(); i++)
5✔
1261
    {
1262
        SetBand(i, new OGCAPIMapWrapperBand(this, i));
4✔
1263
    }
1264
    SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1✔
1265

1266
    return true;
1✔
1267
}
1268

1269
/************************************************************************/
1270
/*                        InitWithCoverageAPI()                         */
1271
/************************************************************************/
1272

1273
bool OGCAPIDataset::InitWithCoverageAPI(GDALOpenInfo *poOpenInfo,
1✔
1274
                                        const CPLString &osCoverageURL,
1275
                                        double dfXMin, double dfYMin,
1276
                                        double dfXMax, double dfYMax,
1277
                                        const CPLJSONObject &oJsonCollection)
1278
{
1279
    int l_nBands = 1;
1✔
1280
    GDALDataType eDT = GDT_Float32;
1✔
1281

1282
    auto oRangeType = oJsonCollection["rangeType"];
3✔
1283
    if (!oRangeType.IsValid())
1✔
1284
        oRangeType = oJsonCollection["rangetype"];
1✔
1285

1286
    auto oDomainSet = oJsonCollection["domainset"];
3✔
1287
    if (!oDomainSet.IsValid())
1✔
1288
        oDomainSet = oJsonCollection["domainSet"];
1✔
1289

1290
    if (!oRangeType.IsValid() || !oDomainSet.IsValid())
1✔
1291
    {
1292
        auto oLinks = oJsonCollection.GetArray("links");
3✔
1293
        for (const auto &oLink : oLinks)
28✔
1294
        {
1295
            const auto osRel = oLink.GetString("rel");
81✔
1296
            const auto osType = oLink.GetString("type");
81✔
1297
            if (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
27✔
1298
                         "coverage-domainset" &&
28✔
1299
                (osType == "application/json" || osType.empty()))
1✔
1300
            {
1301
                CPLString osURL = BuildURL(oLink["href"].ToString());
3✔
1302
                CPLJSONDocument oDoc;
2✔
1303
                if (DownloadJSon(osURL.c_str(), oDoc))
1✔
1304
                {
1305
                    oDomainSet = oDoc.GetRoot();
1✔
1306
                }
1307
            }
1308
            else if (osRel == "http://www.opengis.net/def/rel/ogc/1.0/"
26✔
1309
                              "coverage-rangetype" &&
27✔
1310
                     (osType == "application/json" || osType.empty()))
1✔
1311
            {
1312
                CPLString osURL = BuildURL(oLink["href"].ToString());
3✔
1313
                CPLJSONDocument oDoc;
2✔
1314
                if (DownloadJSon(osURL.c_str(), oDoc))
1✔
1315
                {
1316
                    oRangeType = oDoc.GetRoot();
1✔
1317
                }
1318
            }
1319
        }
1320
    }
1321

1322
    if (oRangeType.IsValid())
1✔
1323
    {
1324
        auto oField = oRangeType.GetArray("field");
3✔
1325
        if (oField.IsValid())
1✔
1326
        {
1327
            l_nBands = oField.Size();
1✔
1328
            // Such as in https://maps.gnosis.earth/ogcapi/collections/NaturalEarth:raster:HYP_HR_SR_OB_DR/coverage/rangetype?f=json
1329
            // https://github.com/opengeospatial/coverage-implementation-schema/blob/main/standard/schemas/1.1/json/examples/generalGrid/2D_regular.json
1330
            std::string osDataType =
1331
                oField[0].GetString("encodingInfo/dataType");
3✔
1332
            if (osDataType.empty())
1✔
1333
            {
1334
                // Older way?
1335
                osDataType = oField[0].GetString("definition");
×
1336
            }
1337
            static const std::map<std::string, GDALDataType> oMapTypes = {
1338
                // https://edc-oapi.dev.hub.eox.at/oapi/collections/S2L2A
1339
                {"UINT8", GDT_Byte},
×
1340
                {"INT16", GDT_Int16},
×
1341
                {"UINT16", GDT_UInt16},
×
1342
                {"INT32", GDT_Int32},
×
1343
                {"UINT32", GDT_UInt32},
×
1344
                {"FLOAT32", GDT_Float32},
×
1345
                {"FLOAT64", GDT_Float64},
×
1346
                // https://test.cubewerx.com/cubewerx/cubeserv/demo/ogcapi/Daraa/collections/Daraa_DTED/coverage/rangetype?f=json
1347
                {"ogcType:unsignedByte", GDT_Byte},
×
1348
                {"ogcType:signedShort", GDT_Int16},
×
1349
                {"ogcType:unsignedShort", GDT_UInt16},
×
1350
                {"ogcType:signedInt", GDT_Int32},
×
1351
                {"ogcType:unsignedInt", GDT_UInt32},
×
1352
                {"ogcType:float32", GDT_Float32},
×
1353
                {"ogcType:float64", GDT_Float64},
×
1354
                {"ogcType:double", GDT_Float64},
×
1355
            };
16✔
1356
            // 08-094r1_SWE_Common_Data_Model_2.0_Submission_Package.pdf page
1357
            // 112
1358
            auto oIter = oMapTypes.find(
1359
                CPLString(osDataType)
1✔
1360
                    .replaceAll("http://www.opengis.net/def/dataType/OGC/0/",
1361
                                "ogcType:"));
1✔
1362
            if (oIter != oMapTypes.end())
1✔
1363
            {
1364
                eDT = oIter->second;
1✔
1365
            }
1366
            else
1367
            {
1368
                CPLDebug("OGCAPI", "Unhandled data type: %s",
×
1369
                         osDataType.c_str());
1370
            }
1371
        }
1372
    }
1373

1374
    CPLString osXAxisName;
2✔
1375
    CPLString osYAxisName;
2✔
1376
    if (oDomainSet.IsValid())
1✔
1377
    {
1378
        auto oAxisLabels = oDomainSet["generalGrid"]["axisLabels"].ToArray();
3✔
1379
        if (oAxisLabels.IsValid() && oAxisLabels.Size() >= 2)
1✔
1380
        {
1381
            osXAxisName = oAxisLabels[0].ToString();
1✔
1382
            osYAxisName = oAxisLabels[1].ToString();
1✔
1383
        }
1384

1385
        auto oAxis = oDomainSet["generalGrid"]["axis"].ToArray();
3✔
1386
        if (oAxis.IsValid() && oAxis.Size() >= 2)
1✔
1387
        {
1388
            double dfXRes = std::abs(oAxis[0].GetDouble("resolution"));
1✔
1389
            double dfYRes = std::abs(oAxis[1].GetDouble("resolution"));
1✔
1390

1391
            dfXMin = oAxis[0].GetDouble("lowerBound");
1✔
1392
            dfXMax = oAxis[0].GetDouble("upperBound");
1✔
1393
            dfYMin = oAxis[1].GetDouble("lowerBound");
1✔
1394
            dfYMax = oAxis[1].GetDouble("upperBound");
1✔
1395

1396
            if (osXAxisName == "Lat")
1✔
1397
            {
1398
                std::swap(dfXRes, dfYRes);
1✔
1399
                std::swap(dfXMin, dfYMin);
1✔
1400
                std::swap(dfXMax, dfYMax);
1✔
1401
            }
1402

1403
            double dfXSize = (dfXMax - dfXMin) / dfXRes;
1✔
1404
            double dfYSize = (dfYMax - dfYMin) / dfYRes;
1✔
1405
            while (dfXSize > INT_MAX || dfYSize > INT_MAX)
1✔
1406
            {
1407
                dfXSize /= 2;
×
1408
                dfYSize /= 2;
×
1409
            }
1410

1411
            nRasterXSize = std::max(1, static_cast<int>(0.5 + dfXSize));
1✔
1412
            nRasterYSize = std::max(1, static_cast<int>(0.5 + dfYSize));
1✔
1413
            m_gt[0] = dfXMin;
1✔
1414
            m_gt[1] = (dfXMax - dfXMin) / nRasterXSize;
1✔
1415
            m_gt[3] = dfYMax;
1✔
1416
            m_gt[5] = -(dfYMax - dfYMin) / nRasterYSize;
1✔
1417
        }
1418

1419
        OGRSpatialReference oSRS;
2✔
1420
        std::string srsName(oDomainSet["generalGrid"].GetString("srsName"));
3✔
1421
        bool bSwap = false;
1✔
1422

1423
        // Strip of time component, as found in
1424
        // OGCAPI:https://maps.ecere.com/ogcapi/collections/blueMarble
1425
        if (STARTS_WITH(srsName.c_str(),
1✔
1426
                        "http://www.opengis.net/def/crs-compound?1=") &&
1✔
1427
            srsName.find("&2=http://www.opengis.net/def/crs/OGC/0/") !=
×
1428
                std::string::npos)
1429
        {
1430
            srsName = srsName.substr(
×
1431
                strlen("http://www.opengis.net/def/crs-compound?1="));
×
1432
            srsName.resize(srsName.find("&2="));
×
1433
        }
1434

1435
        if (oSRS.SetFromUserInput(
1✔
1436
                srsName.c_str(),
1437
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1✔
1438
            OGRERR_NONE)
1439
        {
1440
            if (oSRS.EPSGTreatsAsLatLong() ||
1✔
1441
                oSRS.EPSGTreatsAsNorthingEasting())
×
1442
            {
1443
                bSwap = true;
1✔
1444
            }
1445
        }
1446
        else if (srsName ==
×
1447
                 "https://ows.rasdaman.org/def/crs/EPSG/0/4326")  // HACK
1448
        {
1449
            bSwap = true;
×
1450
        }
1451
        if (bSwap)
1✔
1452
        {
1453
            std::swap(osXAxisName, osYAxisName);
1✔
1454
        }
1455
    }
1456

1457
    int nOverviewCount = 0;
1✔
1458
    int nLargestDim = std::max(nRasterXSize, nRasterYSize);
1✔
1459
    while (nLargestDim > 256)
12✔
1460
    {
1461
        nOverviewCount++;
11✔
1462
        nLargestDim /= 2;
11✔
1463
    }
1464

1465
    m_oSRS.importFromEPSG(4326);
1✔
1466
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1✔
1467

1468
    CPLString osCoverageURLModified(osCoverageURL);
2✔
1469
    if (osCoverageURLModified.find('&') == std::string::npos &&
2✔
1470
        osCoverageURLModified.find('?') == std::string::npos)
1✔
1471
    {
1472
        osCoverageURLModified += '?';
×
1473
    }
1474
    else
1475
    {
1476
        osCoverageURLModified += '&';
1✔
1477
    }
1478

1479
    if (!osXAxisName.empty() && !osYAxisName.empty())
1✔
1480
    {
1481
        osCoverageURLModified +=
1482
            CPLSPrintf("subset=%s(${minx}:${maxx}),%s(${miny}:${maxy})&"
1483
                       "scaleSize=%s(${width}),%s(${height})",
1484
                       osXAxisName.c_str(), osYAxisName.c_str(),
1485
                       osXAxisName.c_str(), osYAxisName.c_str());
1✔
1486
    }
1487
    else
1488
    {
1489
        // FIXME
1490
        osCoverageURLModified += "bbox=${minx},${miny},${maxx},${maxy}&"
1491
                                 "scaleSize=Lat(${height}),Long(${width})";
×
1492
    }
1493

1494
    const bool bCache = CPLTestBool(
1✔
1495
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
1✔
1496
    const int nMaxConnections = atoi(
1✔
1497
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
1✔
1498
                             CPLGetConfigOption("GDAL_MAX_CONNECTIONS", "5")));
1499
    CPLString osWMS_XML;
2✔
1500
    char *pszEscapedURL = CPLEscapeString(osCoverageURLModified, -1, CPLES_XML);
1✔
1501
    std::string osAccept("<Accept>image/tiff;application=geotiff</Accept>");
2✔
1502
    osWMS_XML.Printf("<GDAL_WMS>"
1✔
1503
                     "    <Service name=\"OGCAPICoverage\">"
1504
                     "        <ServerUrl>%s</ServerUrl>"
1505
                     "    </Service>"
1506
                     "    <DataWindow>"
1507
                     "        <UpperLeftX>%.17g</UpperLeftX>"
1508
                     "        <UpperLeftY>%.17g</UpperLeftY>"
1509
                     "        <LowerRightX>%.17g</LowerRightX>"
1510
                     "        <LowerRightY>%.17g</LowerRightY>"
1511
                     "        <SizeX>%d</SizeX>"
1512
                     "        <SizeY>%d</SizeY>"
1513
                     "    </DataWindow>"
1514
                     "    <OverviewCount>%d</OverviewCount>"
1515
                     "    <BlockSizeX>256</BlockSizeX>"
1516
                     "    <BlockSizeY>256</BlockSizeY>"
1517
                     "    <BandsCount>%d</BandsCount>"
1518
                     "    <DataType>%s</DataType>"
1519
                     "    <MaxConnections>%d</MaxConnections>"
1520
                     "    %s"
1521
                     "    %s"
1522
                     "</GDAL_WMS>",
1523
                     pszEscapedURL, dfXMin, dfYMax, dfXMax, dfYMin,
1524
                     nRasterXSize, nRasterYSize, nOverviewCount, l_nBands,
1525
                     GDALGetDataTypeName(eDT), nMaxConnections,
1526
                     osAccept.c_str(), bCache ? "<Cache />" : "");
1✔
1527
    CPLFree(pszEscapedURL);
1✔
1528
    CPLDebug("OGCAPI", "%s", osWMS_XML.c_str());
1✔
1529
    m_poWMSDS.reset(
1✔
1530
        GDALDataset::Open(osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
1531
    if (m_poWMSDS == nullptr)
1✔
1532
        return false;
×
1533

1534
    for (int i = 1; i <= m_poWMSDS->GetRasterCount(); i++)
2✔
1535
    {
1536
        SetBand(i, new OGCAPIMapWrapperBand(this, i));
1✔
1537
    }
1538
    SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1✔
1539

1540
    return true;
1✔
1541
}
1542

1543
/************************************************************************/
1544
/*                      OGCAPIMapWrapperBand()                          */
1545
/************************************************************************/
1546

1547
OGCAPIMapWrapperBand::OGCAPIMapWrapperBand(OGCAPIDataset *poDSIn, int nBandIn)
5✔
1548
{
1549
    poDS = poDSIn;
5✔
1550
    nBand = nBandIn;
5✔
1551
    eDataType = poDSIn->m_poWMSDS->GetRasterBand(nBand)->GetRasterDataType();
5✔
1552
    poDSIn->m_poWMSDS->GetRasterBand(nBand)->GetBlockSize(&nBlockXSize,
5✔
1553
                                                          &nBlockYSize);
1554
}
5✔
1555

1556
/************************************************************************/
1557
/*                            IReadBlock()                              */
1558
/************************************************************************/
1559

1560
CPLErr OGCAPIMapWrapperBand::IReadBlock(int nBlockXOff, int nBlockYOff,
×
1561
                                        void *pImage)
1562
{
1563
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
×
1564
    return poGDS->m_poWMSDS->GetRasterBand(nBand)->ReadBlock(
×
1565
        nBlockXOff, nBlockYOff, pImage);
×
1566
}
1567

1568
/************************************************************************/
1569
/*                             IRasterIO()                              */
1570
/************************************************************************/
1571

1572
CPLErr OGCAPIMapWrapperBand::IRasterIO(
1✔
1573
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1574
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1575
    GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
1576
{
1577
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
1✔
1578
    return poGDS->m_poWMSDS->GetRasterBand(nBand)->RasterIO(
1✔
1579
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1580
        eBufType, nPixelSpace, nLineSpace, psExtraArg);
1✔
1581
}
1582

1583
/************************************************************************/
1584
/*                         GetOverviewCount()                           */
1585
/************************************************************************/
1586

1587
int OGCAPIMapWrapperBand::GetOverviewCount()
4✔
1588
{
1589
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
4✔
1590
    return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetOverviewCount();
4✔
1591
}
1592

1593
/************************************************************************/
1594
/*                              GetOverview()                           */
1595
/************************************************************************/
1596

1597
GDALRasterBand *OGCAPIMapWrapperBand::GetOverview(int nLevel)
×
1598
{
1599
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
×
1600
    return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetOverview(nLevel);
×
1601
}
1602

1603
/************************************************************************/
1604
/*                   GetColorInterpretation()                           */
1605
/************************************************************************/
1606

1607
GDALColorInterp OGCAPIMapWrapperBand::GetColorInterpretation()
6✔
1608
{
1609
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
6✔
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
    if (eDataType == GDT_Byte)
6✔
1613
        return poGDS->m_poWMSDS->GetRasterBand(nBand)->GetColorInterpretation();
5✔
1614
    return GCI_Undefined;
1✔
1615
}
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
{
1626
    CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
×
1627

1628
    std::vector<GMLFeatureClass *> apoClasses;
×
1629
    bool bFullyUnderstood = false;
×
1630
    bool bUseSchemaImports = false;
×
1631
    bool bHaveSchema = GMLParseXSD(osURL.c_str(), bUseSchemaImports, apoClasses,
×
1632
                                   bFullyUnderstood);
1633
    if (bHaveSchema && apoClasses.size() == 1)
×
1634
    {
1635
        auto poGMLFeatureClass = apoClasses[0];
×
1636
        if (poGMLFeatureClass->GetGeometryPropertyCount() == 1 &&
×
1637
            poGMLFeatureClass->GetGeometryProperty(0)->GetType() != wkbUnknown)
×
1638
        {
1639
            eGeomType = static_cast<OGRwkbGeometryType>(
×
1640
                poGMLFeatureClass->GetGeometryProperty(0)->GetType());
×
1641
        }
1642

1643
        const int nPropertyCount = poGMLFeatureClass->GetPropertyCount();
×
1644
        for (int iField = 0; iField < nPropertyCount; iField++)
×
1645
        {
1646
            const auto poProperty = poGMLFeatureClass->GetProperty(iField);
×
1647
            OGRFieldSubType eSubType = OFSTNone;
×
1648
            const OGRFieldType eFType =
1649
                GML_GetOGRFieldType(poProperty->GetType(), eSubType);
×
1650

1651
            const char *pszName = poProperty->GetName();
×
1652
            auto poField = std::make_unique<OGRFieldDefn>(pszName, eFType);
×
1653
            poField->SetSubType(eSubType);
×
1654
            apoFields.emplace_back(std::move(poField));
×
1655
        }
1656
        delete poGMLFeatureClass;
×
1657
        return true;
×
1658
    }
1659

1660
    for (auto poFeatureClass : apoClasses)
×
1661
        delete poFeatureClass;
×
1662

1663
    return false;
×
1664
}
1665

1666
/************************************************************************/
1667
/*                         InitWithTilesAPI()                           */
1668
/************************************************************************/
1669

1670
bool OGCAPIDataset::InitWithTilesAPI(GDALOpenInfo *poOpenInfo,
18✔
1671
                                     const CPLString &osTilesURL, bool bIsMap,
1672
                                     double dfXMin, double dfYMin,
1673
                                     double dfXMax, double dfYMax,
1674
                                     bool bBBOXIsInCRS84,
1675
                                     const CPLJSONObject &oJsonCollection)
1676
{
1677
    CPLJSONDocument oDoc;
36✔
1678
    if (!DownloadJSon(osTilesURL.c_str(), oDoc))
18✔
1679
        return false;
×
1680

1681
    auto oTilesets = oDoc.GetRoot()["tilesets"].ToArray();
54✔
1682
    if (oTilesets.Size() == 0)
18✔
1683
    {
1684
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tilesets");
×
1685
        return false;
×
1686
    }
1687
    const char *pszRequiredTileMatrixSet =
1688
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILEMATRIXSET");
18✔
1689
    const char *pszPreferredTileMatrixSet = CSLFetchNameValue(
36✔
1690
        poOpenInfo->papszOpenOptions, "PREFERRED_TILEMATRIXSET");
18✔
1691
    CPLString osTilesetURL;
36✔
1692
    for (const auto &oTileset : oTilesets)
180✔
1693
    {
1694
        const auto oTileMatrixSetURI = oTileset.GetString("tileMatrixSetURI");
324✔
1695
        const auto oLinks = oTileset.GetArray("links");
324✔
1696
        if (bIsMap)
162✔
1697
        {
1698
            if (oTileset.GetString("dataType") != "map")
117✔
1699
                continue;
×
1700
        }
1701
        else
1702
        {
1703
            if (oTileset.GetString("dataType") != "vector")
45✔
1704
                continue;
×
1705
        }
1706
        if (!oLinks.IsValid())
162✔
1707
        {
1708
            CPLDebug("OGCAPI", "Missing links for a tileset");
×
1709
            continue;
×
1710
        }
1711
        if (pszRequiredTileMatrixSet != nullptr &&
225✔
1712
            oTileMatrixSetURI.find(pszRequiredTileMatrixSet) ==
63✔
1713
                std::string::npos)
1714
        {
1715
            continue;
56✔
1716
        }
1717
        CPLString osCandidateTilesetURL;
212✔
1718
        for (const auto &oLink : oLinks)
318✔
1719
        {
1720
            if (oLink["rel"].ToString() == "self")
212✔
1721
            {
1722
                const auto osType = oLink["type"].ToString();
212✔
1723
                if (osType == MEDIA_TYPE_JSON)
106✔
1724
                {
1725
                    osCandidateTilesetURL = BuildURL(oLink["href"].ToString());
106✔
1726
                    break;
106✔
1727
                }
1728
                else if (osType.empty())
×
1729
                {
1730
                    osCandidateTilesetURL = BuildURL(oLink["href"].ToString());
×
1731
                }
1732
            }
1733
        }
1734
        if (pszRequiredTileMatrixSet != nullptr)
106✔
1735
        {
1736
            osTilesetURL = std::move(osCandidateTilesetURL);
7✔
1737
        }
1738
        else if (pszPreferredTileMatrixSet != nullptr &&
99✔
1739
                 !osCandidateTilesetURL.empty() &&
99✔
1740
                 (oTileMatrixSetURI.find(pszPreferredTileMatrixSet) !=
×
1741
                  std::string::npos))
1742
        {
1743
            osTilesetURL = std::move(osCandidateTilesetURL);
×
1744
        }
1745
        else if (oTileMatrixSetURI.find("WorldCRS84Quad") != std::string::npos)
99✔
1746
        {
1747
            osTilesetURL = std::move(osCandidateTilesetURL);
11✔
1748
        }
1749
        else if (osTilesetURL.empty())
88✔
1750
        {
1751
            osTilesetURL = std::move(osCandidateTilesetURL);
11✔
1752
        }
1753
    }
1754
    if (osTilesetURL.empty())
18✔
1755
    {
1756
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tilematrixset");
×
1757
        return false;
×
1758
    }
1759

1760
    // Download and parse selected tileset definition
1761
    if (!DownloadJSon(osTilesetURL.c_str(), oDoc))
18✔
1762
        return false;
×
1763

1764
    const auto oLinks = oDoc.GetRoot().GetArray("links");
54✔
1765
    if (!oLinks.IsValid())
18✔
1766
    {
1767
        CPLError(CE_Failure, CPLE_AppDefined, "Missing links for tileset");
×
1768
        return false;
×
1769
    }
1770

1771
    // Key - mime type, Value url
1772
    std::map<std::string, std::string> oMapItemUrls;
36✔
1773
    CPLString osMVT_URL;
36✔
1774
    CPLString osGEOJSON_URL;
36✔
1775
    CPLString osTilingSchemeURL;
36✔
1776
    bool bTilingSchemeURLJson = false;
18✔
1777

1778
    for (const auto &oLink : oLinks)
203✔
1779
    {
1780
        const auto osRel = oLink.GetString("rel");
555✔
1781
        const auto osType = oLink.GetString("type");
555✔
1782

1783
        if (!bTilingSchemeURLJson &&
275✔
1784
            osRel == "http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme")
90✔
1785
        {
1786
            if (osType == MEDIA_TYPE_JSON)
18✔
1787
            {
1788
                bTilingSchemeURLJson = true;
18✔
1789
                osTilingSchemeURL = BuildURL(oLink["href"].ToString());
18✔
1790
            }
1791
            else if (osType.empty())
×
1792
            {
1793
                osTilingSchemeURL = BuildURL(oLink["href"].ToString());
×
1794
            }
1795
        }
1796
        else if (bIsMap)
167✔
1797
        {
1798
            if (osRel == "item" && !osType.empty())
117✔
1799
            {
1800
                oMapItemUrls[osType] = BuildURL(oLink["href"].ToString());
52✔
1801
            }
1802
            else if (osRel == "item")
65✔
1803
            {
1804
                // For lack of additional information assume we are getting some bytes
1805
                oMapItemUrls["application/octet-stream"] =
×
1806
                    BuildURL(oLink["href"].ToString());
×
1807
            }
1808
        }
1809
        else
1810
        {
1811
            if (osRel == "item" &&
75✔
1812
                osType == "application/vnd.mapbox-vector-tile")
25✔
1813
            {
1814
                osMVT_URL = BuildURL(oLink["href"].ToString());
5✔
1815
            }
1816
            else if (osRel == "item" && osType == "application/geo+json")
45✔
1817
            {
1818
                osGEOJSON_URL = BuildURL(oLink["href"].ToString());
5✔
1819
            }
1820
        }
1821
    }
1822

1823
    if (osTilingSchemeURL.empty())
18✔
1824
    {
1825
        CPLError(
×
1826
            CE_Failure, CPLE_AppDefined,
1827
            "Cannot find http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme");
1828
        return false;
×
1829
    }
1830

1831
    // Parse tile matrix set limits.
1832
    const auto oTileMatrixSetLimits =
1833
        oDoc.GetRoot().GetArray("tileMatrixSetLimits");
54✔
1834

1835
    struct Limits
1836
    {
1837
        int minTileRow;
1838
        int maxTileRow;
1839
        int minTileCol;
1840
        int maxTileCol;
1841
    };
1842

1843
    std::map<CPLString, Limits> oMapTileMatrixSetLimits;
36✔
1844
    if (CPLTestBool(
18✔
1845
            CPLGetConfigOption("GDAL_OGCAPI_TILEMATRIXSET_LIMITS", "YES")))
1846
    {
1847
        for (const auto &jsonLimit : oTileMatrixSetLimits)
172✔
1848
        {
1849
            const auto osTileMatrix = jsonLimit.GetString("tileMatrix");
308✔
1850
            if (!osTileMatrix.empty())
154✔
1851
            {
1852
                Limits limits;
1853
                limits.minTileRow = jsonLimit.GetInteger("minTileRow");
154✔
1854
                limits.maxTileRow = jsonLimit.GetInteger("maxTileRow");
154✔
1855
                limits.minTileCol = jsonLimit.GetInteger("minTileCol");
154✔
1856
                limits.maxTileCol = jsonLimit.GetInteger("maxTileCol");
154✔
1857
                if (limits.minTileRow > limits.maxTileRow)
154✔
1858
                    continue;  // shouldn't happen on valid data
×
1859
                oMapTileMatrixSetLimits[osTileMatrix] = limits;
154✔
1860
            }
1861
        }
1862
    }
1863

1864
    const std::pair<std::string, std::string> oContentUrlPair =
1865
        SelectImageURL(poOpenInfo->papszOpenOptions, oMapItemUrls);
36✔
1866
    const std::string osContentType = oContentUrlPair.first;
36✔
1867
    const std::string osRasterURL = oContentUrlPair.second;
36✔
1868

1869
    const CPLString osVectorURL = SelectVectorFormatURL(
1870
        poOpenInfo->papszOpenOptions, osMVT_URL, osGEOJSON_URL);
36✔
1871
    if (osRasterURL.empty() && osVectorURL.empty())
18✔
1872
    {
1873
        CPLError(CE_Failure, CPLE_AppDefined,
×
1874
                 "Cannot find link to PNG, JPEG, MVT or GeoJSON tiles");
1875
        return false;
×
1876
    }
1877

1878
    for (const char *pszNeedle : {"{tileMatrix}", "{tileRow}", "{tileCol}"})
72✔
1879
    {
1880
        if (!osRasterURL.empty() &&
93✔
1881
            osRasterURL.find(pszNeedle) == std::string::npos)
39✔
1882
        {
1883
            CPLError(CE_Failure, CPLE_AppDefined, "%s missing in tile URL %s",
×
1884
                     pszNeedle, osRasterURL.c_str());
1885
            return false;
×
1886
        }
1887
        if (!osVectorURL.empty() &&
69✔
1888
            osVectorURL.find(pszNeedle) == std::string::npos)
15✔
1889
        {
1890
            CPLError(CE_Failure, CPLE_AppDefined, "%s missing in tile URL %s",
×
1891
                     pszNeedle, osVectorURL.c_str());
1892
            return false;
×
1893
        }
1894
    }
1895

1896
    // Download and parse tile matrix set definition
1897
    if (!DownloadJSon(osTilingSchemeURL.c_str(), oDoc, nullptr,
18✔
1898
                      MEDIA_TYPE_JSON))
1899
        return false;
×
1900

1901
    auto tms = gdal::TileMatrixSet::parse(oDoc.SaveAsString().c_str());
36✔
1902
    if (tms == nullptr)
18✔
1903
        return false;
×
1904

1905
    if (m_oSRS.SetFromUserInput(
36✔
1906
            tms->crs().c_str(),
18✔
1907
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
18✔
1908
        OGRERR_NONE)
1909
        return false;
×
1910
    const bool bInvertAxis = m_oSRS.EPSGTreatsAsLatLong() != FALSE ||
36✔
1911
                             m_oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
18✔
1912
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
18✔
1913

1914
    bool bFoundSomething = false;
18✔
1915
    if (!osVectorURL.empty() && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
18✔
1916
    {
1917
        const auto osVectorType = oJsonCollection.GetString("vectorType");
15✔
1918
        OGRwkbGeometryType eGeomType = wkbUnknown;
5✔
1919
        if (osVectorType == "Points")
5✔
1920
            eGeomType = wkbPoint;
×
1921
        else if (osVectorType == "Lines")
5✔
1922
            eGeomType = wkbMultiLineString;
×
1923
        else if (osVectorType == "Polygons")
5✔
1924
            eGeomType = wkbMultiPolygon;
×
1925

1926
        CPLString osXMLSchemaURL;
10✔
1927
        for (const auto &oLink : oJsonCollection.GetArray("links"))
180✔
1928
        {
1929
            if (oLink["rel"].ToString() == "describedBy" &&
350✔
1930
                oLink["type"].ToString() == "text/xml")
175✔
1931
            {
1932
                osXMLSchemaURL = BuildURL(oLink["href"].ToString());
×
1933
            }
1934
        }
1935

1936
        std::vector<std::unique_ptr<OGRFieldDefn>> apoFields;
5✔
1937
        bool bGotSchema = false;
5✔
1938
        if (!osXMLSchemaURL.empty())
5✔
1939
        {
1940
            bGotSchema = ParseXMLSchema(osXMLSchemaURL, apoFields, eGeomType);
×
1941
        }
1942

1943
        for (const auto &tileMatrix : tms->tileMatrixList())
155✔
1944
        {
1945
            const double dfOriX =
150✔
1946
                bInvertAxis ? tileMatrix.mTopLeftY : tileMatrix.mTopLeftX;
150✔
1947
            const double dfOriY =
150✔
1948
                bInvertAxis ? tileMatrix.mTopLeftX : tileMatrix.mTopLeftY;
150✔
1949

1950
            auto oLimitsIter = oMapTileMatrixSetLimits.find(tileMatrix.mId);
150✔
1951
            if (!oMapTileMatrixSetLimits.empty() &&
300✔
1952
                oLimitsIter == oMapTileMatrixSetLimits.end())
300✔
1953
            {
1954
                // Tile matrix level not in known limits
1955
                continue;
115✔
1956
            }
1957
            int minCol = std::max(
35✔
1958
                0, static_cast<int>((dfXMin - dfOriX) / tileMatrix.mResX /
70✔
1959
                                    tileMatrix.mTileWidth));
35✔
1960
            int maxCol =
1961
                std::min(tileMatrix.mMatrixWidth - 1,
70✔
1962
                         static_cast<int>((dfXMax - dfOriX) / tileMatrix.mResX /
70✔
1963
                                          tileMatrix.mTileWidth));
35✔
1964
            int minRow = std::max(
35✔
1965
                0, static_cast<int>((dfOriY - dfYMax) / tileMatrix.mResY /
70✔
1966
                                    tileMatrix.mTileHeight));
35✔
1967
            int maxRow =
1968
                std::min(tileMatrix.mMatrixHeight - 1,
70✔
1969
                         static_cast<int>((dfOriY - dfYMin) / tileMatrix.mResY /
70✔
1970
                                          tileMatrix.mTileHeight));
35✔
1971
            if (oLimitsIter != oMapTileMatrixSetLimits.end())
35✔
1972
            {
1973
                // Take into account tileMatrixSetLimits
1974
                minCol = std::max(minCol, oLimitsIter->second.minTileCol);
35✔
1975
                minRow = std::max(minRow, oLimitsIter->second.minTileRow);
35✔
1976
                maxCol = std::min(maxCol, oLimitsIter->second.maxTileCol);
35✔
1977
                maxRow = std::min(maxRow, oLimitsIter->second.maxTileRow);
35✔
1978
                if (minCol > maxCol || minRow > maxRow)
35✔
1979
                {
1980
                    continue;
×
1981
                }
1982
            }
1983
            auto poLayer =
1984
                std::unique_ptr<OGCAPITiledLayer>(new OGCAPITiledLayer(
1985
                    this, bInvertAxis, osVectorURL, osVectorURL == osMVT_URL,
35✔
1986
                    tileMatrix, eGeomType));
70✔
1987
            poLayer->SetMinMaxXY(minCol, minRow, maxCol, maxRow);
35✔
1988
            poLayer->SetExtent(dfXMin, dfYMin, dfXMax, dfYMax);
35✔
1989
            if (bGotSchema)
35✔
1990
                poLayer->SetFields(apoFields);
×
1991
            m_apoLayers.emplace_back(std::move(poLayer));
35✔
1992
        }
1993

1994
        bFoundSomething = true;
5✔
1995
    }
1996

1997
    if (!osRasterURL.empty() && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0)
18✔
1998
    {
1999
        if (bBBOXIsInCRS84)
8✔
2000
        {
2001
            // Reproject the extent if needed
2002
            OGRSpatialReference oCRS84;
16✔
2003
            oCRS84.importFromEPSG(4326);
8✔
2004
            oCRS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
8✔
2005
            auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
2006
                OGRCreateCoordinateTransformation(&oCRS84, &m_oSRS));
16✔
2007
            if (poCT)
8✔
2008
            {
2009
                poCT->TransformBounds(dfXMin, dfYMin, dfXMax, dfYMax, &dfXMin,
8✔
2010
                                      &dfYMin, &dfXMax, &dfYMax, 21);
8✔
2011
            }
2012
        }
2013

2014
        const bool bCache = CPLTestBool(
8✔
2015
            CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "YES"));
8✔
2016
        const int nMaxConnections = atoi(CSLFetchNameValueDef(
8✔
2017
            poOpenInfo->papszOpenOptions, "MAX_CONNECTIONS",
8✔
2018
            CPLGetConfigOption("GDAL_WMS_MAX_CONNECTIONS", "5")));
2019
        const char *pszTileMatrix =
2020
            CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILEMATRIX");
8✔
2021

2022
        int l_nBands = FigureBands(osContentType, osRasterURL);
8✔
2023

2024
        for (const auto &tileMatrix : tms->tileMatrixList())
191✔
2025
        {
2026
            if (pszTileMatrix && !EQUAL(tileMatrix.mId.c_str(), pszTileMatrix))
191✔
2027
            {
2028
                continue;
99✔
2029
            }
2030
            if (tileMatrix.mTileWidth == 0 ||
191✔
2031
                tileMatrix.mMatrixWidth > INT_MAX / tileMatrix.mTileWidth ||
191✔
2032
                tileMatrix.mTileHeight == 0 ||
183✔
2033
                tileMatrix.mMatrixHeight > INT_MAX / tileMatrix.mTileHeight)
183✔
2034
            {
2035
                // Too resoluted for GDAL limits
2036
                break;
2037
            }
2038
            auto oLimitsIter = oMapTileMatrixSetLimits.find(tileMatrix.mId);
183✔
2039
            if (!oMapTileMatrixSetLimits.empty() &&
366✔
2040
                oLimitsIter == oMapTileMatrixSetLimits.end())
366✔
2041
            {
2042
                // Tile matrix level not in known limits
2043
                continue;
99✔
2044
            }
2045

2046
            if (dfXMax - dfXMin < tileMatrix.mResX ||
84✔
2047
                dfYMax - dfYMin < tileMatrix.mResY)
84✔
2048
            {
2049
                // skip levels for which the extent is smaller than the size
2050
                // of one pixel
2051
                continue;
×
2052
            }
2053

2054
            CPLString osURL(osRasterURL);
84✔
2055
            osURL.replaceAll("{tileMatrix}", tileMatrix.mId.c_str());
84✔
2056
            osURL.replaceAll("{tileRow}", "${y}");
84✔
2057
            osURL.replaceAll("{tileCol}", "${x}");
84✔
2058

2059
            const double dfOriX =
84✔
2060
                bInvertAxis ? tileMatrix.mTopLeftY : tileMatrix.mTopLeftX;
84✔
2061
            const double dfOriY =
84✔
2062
                bInvertAxis ? tileMatrix.mTopLeftX : tileMatrix.mTopLeftY;
84✔
2063

2064
            const auto CreateWMS_XML =
2065
                [=, &osURL, &tileMatrix](int minRow, int rowCount,
84✔
2066
                                         int nCoalesce, double &dfStripMinY,
2067
                                         double &dfStripMaxY)
252✔
2068
            {
2069
                int minCol = 0;
84✔
2070
                int maxCol = tileMatrix.mMatrixWidth - 1;
84✔
2071
                int maxRow = minRow + rowCount - 1;
84✔
2072
                double dfStripMinX =
84✔
2073
                    dfOriX + minCol * tileMatrix.mTileWidth * tileMatrix.mResX;
84✔
2074
                double dfStripMaxX = dfOriX + (maxCol + 1) *
84✔
2075
                                                  tileMatrix.mTileWidth *
84✔
2076
                                                  tileMatrix.mResX;
84✔
2077
                dfStripMaxY =
84✔
2078
                    dfOriY - minRow * tileMatrix.mTileHeight * tileMatrix.mResY;
84✔
2079
                dfStripMinY = dfOriY - (maxRow + 1) * tileMatrix.mTileHeight *
84✔
2080
                                           tileMatrix.mResY;
84✔
2081
                CPLString osWMS_XML;
84✔
2082
                char *pszEscapedURL = CPLEscapeString(osURL, -1, CPLES_XML);
84✔
2083
                osWMS_XML.Printf(
2084
                    "<GDAL_WMS>"
2085
                    "    <Service name=\"TMS\">"
2086
                    "        <ServerUrl>%s</ServerUrl>"
2087
                    "        <TileXMultiplier>%d</TileXMultiplier>"
2088
                    "    </Service>"
2089
                    "    <DataWindow>"
2090
                    "        <UpperLeftX>%.17g</UpperLeftX>"
2091
                    "        <UpperLeftY>%.17g</UpperLeftY>"
2092
                    "        <LowerRightX>%.17g</LowerRightX>"
2093
                    "        <LowerRightY>%.17g</LowerRightY>"
2094
                    "        <TileLevel>0</TileLevel>"
2095
                    "        <TileY>%d</TileY>"
2096
                    "        <SizeX>%d</SizeX>"
2097
                    "        <SizeY>%d</SizeY>"
2098
                    "        <YOrigin>top</YOrigin>"
2099
                    "    </DataWindow>"
2100
                    "    <BlockSizeX>%d</BlockSizeX>"
2101
                    "    <BlockSizeY>%d</BlockSizeY>"
2102
                    "    <BandsCount>%d</BandsCount>"
2103
                    "    <MaxConnections>%d</MaxConnections>"
2104
                    "    %s"
2105
                    "</GDAL_WMS>",
2106
                    pszEscapedURL, nCoalesce, dfStripMinX, dfStripMaxY,
2107
                    dfStripMaxX, dfStripMinY, minRow,
2108
                    (maxCol - minCol + 1) / nCoalesce * tileMatrix.mTileWidth,
84✔
2109
                    rowCount * tileMatrix.mTileHeight, tileMatrix.mTileWidth,
84✔
2110
                    tileMatrix.mTileHeight, l_nBands, nMaxConnections,
84✔
2111
                    bCache ? "<Cache />" : "");
84✔
2112
                CPLFree(pszEscapedURL);
84✔
2113
                return osWMS_XML;
84✔
2114
            };
84✔
2115

2116
            auto vmwl = tileMatrix.mVariableMatrixWidthList;
84✔
2117
            if (vmwl.empty())
84✔
2118
            {
2119
                double dfIgnored1, dfIgnored2;
2120
                CPLString osWMS_XML(CreateWMS_XML(0, tileMatrix.mMatrixHeight,
84✔
2121
                                                  1, dfIgnored1, dfIgnored2));
84✔
2122
                if (osWMS_XML.empty())
84✔
2123
                    continue;
×
2124
                std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
2125
                    osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
84✔
2126
                if (!poDS)
84✔
2127
                    return false;
×
2128
                m_apoDatasetsAssembled.emplace_back(std::move(poDS));
84✔
2129
            }
2130
            else
2131
            {
2132
                std::sort(vmwl.begin(), vmwl.end(),
×
2133
                          [](const gdal::TileMatrixSet::TileMatrix::
×
2134
                                 VariableMatrixWidth &a,
2135
                             const gdal::TileMatrixSet::TileMatrix::
2136
                                 VariableMatrixWidth &b)
2137
                          { return a.mMinTileRow < b.mMinTileRow; });
×
2138
                std::vector<GDALDatasetH> apoStrippedDS;
×
2139
                // For each variable matrix width, create a separate WMS dataset
2140
                // with the correspond strip
2141
                for (size_t i = 0; i < vmwl.size(); i++)
×
2142
                {
2143
                    if (vmwl[i].mCoalesce <= 0 ||
×
2144
                        (tileMatrix.mMatrixWidth % vmwl[i].mCoalesce) != 0)
×
2145
                    {
2146
                        CPLError(CE_Failure, CPLE_AppDefined,
×
2147
                                 "Invalid coalesce factor (%d) w.r.t matrix "
2148
                                 "width (%d)",
2149
                                 vmwl[i].mCoalesce, tileMatrix.mMatrixWidth);
×
2150
                        return false;
×
2151
                    }
2152
                    {
2153
                        double dfStripMinY = 0;
×
2154
                        double dfStripMaxY = 0;
×
2155
                        CPLString osWMS_XML(CreateWMS_XML(
2156
                            vmwl[i].mMinTileRow,
×
2157
                            vmwl[i].mMaxTileRow - vmwl[i].mMinTileRow + 1,
×
2158
                            vmwl[i].mCoalesce, dfStripMinY, dfStripMaxY));
×
2159
                        if (osWMS_XML.empty())
×
2160
                            continue;
×
2161
                        if (dfStripMinY < dfYMax && dfStripMaxY > dfYMin)
×
2162
                        {
2163
                            std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
2164
                                osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
×
2165
                            if (!poDS)
×
2166
                                return false;
×
2167
                            m_apoDatasetsElementary.emplace_back(
2168
                                std::move(poDS));
×
2169
                            apoStrippedDS.emplace_back(GDALDataset::ToHandle(
×
2170
                                m_apoDatasetsElementary.back().get()));
×
2171
                        }
2172
                    }
2173

2174
                    // Add a strip for non-coalesced tiles
2175
                    if (i + 1 < vmwl.size() &&
×
2176
                        vmwl[i].mMaxTileRow + 1 != vmwl[i + 1].mMinTileRow)
×
2177
                    {
2178
                        double dfStripMinY = 0;
×
2179
                        double dfStripMaxY = 0;
×
2180
                        CPLString osWMS_XML(CreateWMS_XML(
2181
                            vmwl[i].mMaxTileRow + 1,
×
2182
                            vmwl[i + 1].mMinTileRow - vmwl[i].mMaxTileRow - 1,
×
2183
                            1, dfStripMinY, dfStripMaxY));
×
2184
                        if (osWMS_XML.empty())
×
2185
                            continue;
×
2186
                        if (dfStripMinY < dfYMax && dfStripMaxY > dfYMin)
×
2187
                        {
2188
                            std::unique_ptr<GDALDataset> poDS(GDALDataset::Open(
2189
                                osWMS_XML, GDAL_OF_RASTER | GDAL_OF_INTERNAL));
×
2190
                            if (!poDS)
×
2191
                                return false;
×
2192
                            m_apoDatasetsElementary.emplace_back(
2193
                                std::move(poDS));
×
2194
                            apoStrippedDS.emplace_back(GDALDataset::ToHandle(
×
2195
                                m_apoDatasetsElementary.back().get()));
×
2196
                        }
2197
                    }
2198
                }
2199

2200
                if (apoStrippedDS.empty())
×
2201
                    return false;
×
2202

2203
                // Assemble the strips in a single VRT
2204
                CPLStringList argv;
×
2205
                argv.AddString("-resolution");
×
2206
                argv.AddString("highest");
×
2207
                GDALBuildVRTOptions *psOptions =
2208
                    GDALBuildVRTOptionsNew(argv.List(), nullptr);
×
2209
                GDALDatasetH hAssembledDS = GDALBuildVRT(
×
2210
                    "", static_cast<int>(apoStrippedDS.size()),
×
2211
                    &apoStrippedDS[0], nullptr, psOptions, nullptr);
×
2212
                GDALBuildVRTOptionsFree(psOptions);
×
2213
                if (hAssembledDS == nullptr)
×
2214
                    return false;
×
2215
                m_apoDatasetsAssembled.emplace_back(
2216
                    GDALDataset::FromHandle(hAssembledDS));
×
2217
            }
2218

2219
            CPLStringList argv;
84✔
2220
            argv.AddString("-of");
84✔
2221
            argv.AddString("VRT");
84✔
2222
            argv.AddString("-projwin");
84✔
2223
            argv.AddString(CPLSPrintf("%.17g", dfXMin));
84✔
2224
            argv.AddString(CPLSPrintf("%.17g", dfYMax));
84✔
2225
            argv.AddString(CPLSPrintf("%.17g", dfXMax));
84✔
2226
            argv.AddString(CPLSPrintf("%.17g", dfYMin));
84✔
2227
            GDALTranslateOptions *psOptions =
2228
                GDALTranslateOptionsNew(argv.List(), nullptr);
84✔
2229
            GDALDatasetH hCroppedDS = GDALTranslate(
84✔
2230
                "", GDALDataset::ToHandle(m_apoDatasetsAssembled.back().get()),
84✔
2231
                psOptions, nullptr);
2232
            GDALTranslateOptionsFree(psOptions);
84✔
2233
            if (hCroppedDS == nullptr)
84✔
2234
                return false;
×
2235
            m_apoDatasetsCropped.emplace_back(
2236
                GDALDataset::FromHandle(hCroppedDS));
84✔
2237

2238
            if (tileMatrix.mResX <= m_gt[1])
84✔
2239
                break;
×
2240
        }
2241
        if (!m_apoDatasetsCropped.empty())
8✔
2242
        {
2243
            std::reverse(std::begin(m_apoDatasetsCropped),
8✔
2244
                         std::end(m_apoDatasetsCropped));
8✔
2245
            nRasterXSize = m_apoDatasetsCropped[0]->GetRasterXSize();
8✔
2246
            nRasterYSize = m_apoDatasetsCropped[0]->GetRasterYSize();
8✔
2247
            m_apoDatasetsCropped[0]->GetGeoTransform(m_gt);
8✔
2248

2249
            for (int i = 1; i <= m_apoDatasetsCropped[0]->GetRasterCount(); i++)
38✔
2250
            {
2251
                SetBand(i, new OGCAPITilesWrapperBand(this, i));
30✔
2252
            }
2253
            SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
8✔
2254

2255
            bFoundSomething = true;
8✔
2256
        }
2257
    }
2258

2259
    return bFoundSomething;
18✔
2260
}
2261

2262
/************************************************************************/
2263
/*                      OGCAPITilesWrapperBand()                        */
2264
/************************************************************************/
2265

2266
OGCAPITilesWrapperBand::OGCAPITilesWrapperBand(OGCAPIDataset *poDSIn,
30✔
2267
                                               int nBandIn)
30✔
2268
{
2269
    poDS = poDSIn;
30✔
2270
    nBand = nBandIn;
30✔
2271
    eDataType = poDSIn->m_apoDatasetsCropped[0]
30✔
2272
                    ->GetRasterBand(nBand)
2273
                    ->GetRasterDataType();
30✔
2274
    poDSIn->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->GetBlockSize(
30✔
2275
        &nBlockXSize, &nBlockYSize);
2276
}
30✔
2277

2278
/************************************************************************/
2279
/*                            IReadBlock()                              */
2280
/************************************************************************/
2281

2282
CPLErr OGCAPITilesWrapperBand::IReadBlock(int nBlockXOff, int nBlockYOff,
1✔
2283
                                          void *pImage)
2284
{
2285
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
1✔
2286
    return poGDS->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->ReadBlock(
1✔
2287
        nBlockXOff, nBlockYOff, pImage);
1✔
2288
}
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
{
2299
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
×
2300

2301
    if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
×
2302
        poGDS->m_apoDatasetsCropped.size() > 1 && eRWFlag == GF_Read)
×
2303
    {
2304
        int bTried;
2305
        CPLErr eErr = TryOverviewRasterIO(
×
2306
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2307
            eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
2308
        if (bTried)
×
2309
            return eErr;
×
2310
    }
2311

2312
    return poGDS->m_apoDatasetsCropped[0]->GetRasterBand(nBand)->RasterIO(
×
2313
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2314
        eBufType, nPixelSpace, nLineSpace, psExtraArg);
×
2315
}
2316

2317
/************************************************************************/
2318
/*                         GetOverviewCount()                           */
2319
/************************************************************************/
2320

2321
int OGCAPITilesWrapperBand::GetOverviewCount()
10✔
2322
{
2323
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
10✔
2324
    return static_cast<int>(poGDS->m_apoDatasetsCropped.size() - 1);
10✔
2325
}
2326

2327
/************************************************************************/
2328
/*                              GetOverview()                           */
2329
/************************************************************************/
2330

2331
GDALRasterBand *OGCAPITilesWrapperBand::GetOverview(int nLevel)
1✔
2332
{
2333
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
1✔
2334
    if (nLevel < 0 || nLevel >= GetOverviewCount())
1✔
2335
        return nullptr;
×
2336
    return poGDS->m_apoDatasetsCropped[nLevel + 1]->GetRasterBand(nBand);
1✔
2337
}
2338

2339
/************************************************************************/
2340
/*                   GetColorInterpretation()                           */
2341
/************************************************************************/
2342

2343
GDALColorInterp OGCAPITilesWrapperBand::GetColorInterpretation()
5✔
2344
{
2345
    OGCAPIDataset *poGDS = cpl::down_cast<OGCAPIDataset *>(poDS);
5✔
2346
    return poGDS->m_apoDatasetsCropped[0]
5✔
2347
        ->GetRasterBand(nBand)
5✔
2348
        ->GetColorInterpretation();
5✔
2349
}
2350

2351
/************************************************************************/
2352
/*                             IRasterIO()                              */
2353
/************************************************************************/
2354

2355
CPLErr OGCAPIDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2✔
2356
                                int nXSize, int nYSize, void *pData,
2357
                                int nBufXSize, int nBufYSize,
2358
                                GDALDataType eBufType, int nBandCount,
2359
                                BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
2360
                                GSpacing nLineSpace, GSpacing nBandSpace,
2361
                                GDALRasterIOExtraArg *psExtraArg)
2362
{
2363
    if (!m_apoDatasetsCropped.empty())
2✔
2364
    {
2365
        // Tiles API
2366
        if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
1✔
2367
            m_apoDatasetsCropped.size() > 1 && eRWFlag == GF_Read)
2✔
2368
        {
2369
            int bTried;
2370
            CPLErr eErr = TryOverviewRasterIO(
×
2371
                eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
2372
                nBufYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
2373
                nLineSpace, nBandSpace, psExtraArg, &bTried);
2374
            if (bTried)
×
2375
                return eErr;
×
2376
        }
2377

2378
        return m_apoDatasetsCropped[0]->RasterIO(
1✔
2379
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2380
            eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
2381
            nBandSpace, psExtraArg);
1✔
2382
    }
2383
    else if (m_poWMSDS)
1✔
2384
    {
2385
        // Maps API
2386
        return m_poWMSDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
1✔
2387
                                   nBufXSize, nBufYSize, eBufType, nBandCount,
2388
                                   panBandMap, nPixelSpace, nLineSpace,
2389
                                   nBandSpace, psExtraArg);
1✔
2390
    }
2391

2392
    // Should not be hit
2393
    return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
×
2394
                                  nBufXSize, nBufYSize, eBufType, nBandCount,
2395
                                  panBandMap, nPixelSpace, nLineSpace,
2396
                                  nBandSpace, psExtraArg);
×
2397
}
2398

2399
/************************************************************************/
2400
/*                         OGCAPITiledLayer()                           */
2401
/************************************************************************/
2402

2403
OGCAPITiledLayer::OGCAPITiledLayer(
35✔
2404
    OGCAPIDataset *poDS, bool bInvertAxis, const CPLString &osTileURL,
2405
    bool bIsMVT, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
2406
    OGRwkbGeometryType eGeomType)
35✔
2407
    : m_poDS(poDS), m_osTileURL(osTileURL), m_bIsMVT(bIsMVT),
2408
      m_oTileMatrix(tileMatrix), m_bInvertAxis(bInvertAxis)
35✔
2409
{
2410
    m_poFeatureDefn = new OGCAPITiledLayerFeatureDefn(
35✔
2411
        this, ("Zoom level " + tileMatrix.mId).c_str());
35✔
2412
    SetDescription(m_poFeatureDefn->GetName());
35✔
2413
    m_poFeatureDefn->SetGeomType(eGeomType);
35✔
2414
    if (eGeomType != wkbNone)
35✔
2415
    {
2416
        auto poClonedSRS = poDS->m_oSRS.Clone();
35✔
2417
        m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poClonedSRS);
35✔
2418
        poClonedSRS->Dereference();
35✔
2419
    }
2420
    m_poFeatureDefn->Reference();
35✔
2421
    m_osTileURL.replaceAll("{tileMatrix}", tileMatrix.mId.c_str());
35✔
2422
}
35✔
2423

2424
/************************************************************************/
2425
/*                        ~OGCAPITiledLayer()                           */
2426
/************************************************************************/
2427

2428
OGCAPITiledLayer::~OGCAPITiledLayer()
70✔
2429
{
2430
    m_poFeatureDefn->InvalidateLayer();
35✔
2431
    m_poFeatureDefn->Release();
35✔
2432
}
70✔
2433

2434
/************************************************************************/
2435
/*                       GetCoalesceFactorForRow()                      */
2436
/************************************************************************/
2437

2438
int OGCAPITiledLayer::GetCoalesceFactorForRow(int nRow) const
40✔
2439
{
2440
    int nCoalesce = 1;
40✔
2441
    for (const auto &vmw : m_oTileMatrix.mVariableMatrixWidthList)
40✔
2442
    {
2443
        if (nRow >= vmw.mMinTileRow && nRow <= vmw.mMaxTileRow)
×
2444
        {
2445
            nCoalesce = vmw.mCoalesce;
×
2446
            break;
×
2447
        }
2448
    }
2449
    return nCoalesce;
40✔
2450
}
2451

2452
/************************************************************************/
2453
/*                         ResetReading()                               */
2454
/************************************************************************/
2455

2456
void OGCAPITiledLayer::ResetReading()
35✔
2457
{
2458
    if (m_nCurX == m_nCurMinX && m_nCurY == m_nCurMinY && m_poUnderlyingLayer)
35✔
2459
    {
2460
        m_poUnderlyingLayer->ResetReading();
×
2461
    }
2462
    else
2463
    {
2464
        m_nCurX = m_nCurMinX;
35✔
2465
        m_nCurY = m_nCurMinY;
35✔
2466
        m_poUnderlyingDS.reset();
35✔
2467
        m_poUnderlyingLayer = nullptr;
35✔
2468
    }
2469
}
35✔
2470

2471
/************************************************************************/
2472
/*                             OpenTile()                               */
2473
/************************************************************************/
2474

2475
GDALDataset *OGCAPITiledLayer::OpenTile(int nX, int nY, bool &bEmptyContent)
20✔
2476
{
2477
    int nCoalesce = GetCoalesceFactorForRow(nY);
20✔
2478
    if (nCoalesce <= 0)
20✔
2479
        return nullptr;
×
2480
    nX = (nX / nCoalesce) * nCoalesce;
20✔
2481

2482
    const char *const *papszOpenOptions = nullptr;
20✔
2483
    CPLString poPrefix;
40✔
2484
    CPLStringList aosOpenOptions;
40✔
2485

2486
    if (m_bIsMVT)
20✔
2487
    {
2488
        const double dfOriX =
12✔
2489
            m_bInvertAxis ? m_oTileMatrix.mTopLeftY : m_oTileMatrix.mTopLeftX;
12✔
2490
        const double dfOriY =
12✔
2491
            m_bInvertAxis ? m_oTileMatrix.mTopLeftX : m_oTileMatrix.mTopLeftY;
12✔
2492
        aosOpenOptions.SetNameValue(
2493
            "@GEOREF_TOPX",
2494
            CPLSPrintf("%.17g", dfOriX + nX * m_oTileMatrix.mResX *
12✔
2495
                                             m_oTileMatrix.mTileWidth));
12✔
2496
        aosOpenOptions.SetNameValue(
2497
            "@GEOREF_TOPY",
2498
            CPLSPrintf("%.17g", dfOriY - nY * m_oTileMatrix.mResY *
12✔
2499
                                             m_oTileMatrix.mTileHeight));
12✔
2500
        aosOpenOptions.SetNameValue(
2501
            "@GEOREF_TILEDIMX",
2502
            CPLSPrintf("%.17g", nCoalesce * m_oTileMatrix.mResX *
12✔
2503
                                    m_oTileMatrix.mTileWidth));
12✔
2504
        aosOpenOptions.SetNameValue(
2505
            "@GEOREF_TILEDIMY",
2506
            CPLSPrintf("%.17g",
2507
                       m_oTileMatrix.mResY * m_oTileMatrix.mTileWidth));
12✔
2508

2509
        papszOpenOptions = aosOpenOptions.List();
12✔
2510
        poPrefix = "MVT";
12✔
2511
    }
2512

2513
    std::unique_ptr<GDALDataset> dataset = m_poDS->OpenTile(
20✔
2514
        m_osTileURL, stoi(m_oTileMatrix.mId), nX, nY, bEmptyContent,
40✔
2515
        GDAL_OF_VECTOR, poPrefix, papszOpenOptions);
40✔
2516

2517
    return dataset.release();
20✔
2518
}
2519

2520
/************************************************************************/
2521
/*                      FinalizeFeatureDefnWithLayer()                  */
2522
/************************************************************************/
2523

2524
void OGCAPITiledLayer::FinalizeFeatureDefnWithLayer(OGRLayer *poUnderlyingLayer)
5✔
2525
{
2526
    if (!m_bFeatureDefnEstablished)
5✔
2527
    {
2528
        m_bFeatureDefnEstablished = true;
5✔
2529
        const auto poSrcFieldDefn = poUnderlyingLayer->GetLayerDefn();
5✔
2530
        const int nFieldCount = poSrcFieldDefn->GetFieldCount();
5✔
2531
        for (int i = 0; i < nFieldCount; i++)
60✔
2532
        {
2533
            m_poFeatureDefn->AddFieldDefn(poSrcFieldDefn->GetFieldDefn(i));
55✔
2534
        }
2535
    }
2536
}
5✔
2537

2538
/************************************************************************/
2539
/*                            BuildFeature()                            */
2540
/************************************************************************/
2541

2542
OGRFeature *OGCAPITiledLayer::BuildFeature(OGRFeature *poSrcFeature, int nX,
5✔
2543
                                           int nY)
2544
{
2545
    int nCoalesce = GetCoalesceFactorForRow(nY);
5✔
2546
    if (nCoalesce <= 0)
5✔
2547
        return nullptr;
×
2548
    nX = (nX / nCoalesce) * nCoalesce;
5✔
2549

2550
    OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
5✔
2551
    const GIntBig nFID = nY * m_oTileMatrix.mMatrixWidth + nX +
5✔
2552
                         poSrcFeature->GetFID() * m_oTileMatrix.mMatrixWidth *
5✔
2553
                             m_oTileMatrix.mMatrixHeight;
5✔
2554
    auto poGeom = poSrcFeature->StealGeometry();
5✔
2555
    if (poGeom && m_poFeatureDefn->GetGeomType() != wkbUnknown)
5✔
2556
    {
2557
        poGeom =
2558
            OGRGeometryFactory::forceTo(poGeom, m_poFeatureDefn->GetGeomType());
×
2559
    }
2560
    poFeature->SetFrom(poSrcFeature, true);
5✔
2561
    poFeature->SetFID(nFID);
5✔
2562
    if (poGeom && m_poFeatureDefn->GetGeomFieldCount() > 0)
5✔
2563
    {
2564
        poGeom->assignSpatialReference(
5✔
2565
            m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
5✔
2566
    }
2567
    poFeature->SetGeometryDirectly(poGeom);
5✔
2568
    delete poSrcFeature;
5✔
2569
    return poFeature;
5✔
2570
}
2571

2572
/************************************************************************/
2573
/*                        IncrementTileIndices()                        */
2574
/************************************************************************/
2575

2576
bool OGCAPITiledLayer::IncrementTileIndices()
15✔
2577
{
2578

2579
    const int nCoalesce = GetCoalesceFactorForRow(m_nCurY);
15✔
2580
    if (nCoalesce <= 0)
15✔
2581
        return false;
×
2582
    if (m_nCurX / nCoalesce < m_nCurMaxX / nCoalesce)
15✔
2583
    {
2584
        m_nCurX += nCoalesce;
15✔
2585
    }
2586
    else if (m_nCurY < m_nCurMaxY)
×
2587
    {
2588
        m_nCurX = m_nCurMinX;
×
2589
        m_nCurY++;
×
2590
    }
2591
    else
2592
    {
2593
        m_nCurY = -1;
×
2594
        return false;
×
2595
    }
2596
    return true;
15✔
2597
}
2598

2599
/************************************************************************/
2600
/*                          GetNextRawFeature()                         */
2601
/************************************************************************/
2602

2603
OGRFeature *OGCAPITiledLayer::GetNextRawFeature()
20✔
2604
{
2605
    while (true)
2606
    {
2607
        if (m_poUnderlyingLayer == nullptr)
20✔
2608
        {
2609
            if (m_nCurY < 0)
20✔
2610
            {
2611
                return nullptr;
×
2612
            }
2613
            bool bEmptyContent = false;
20✔
2614
            m_poUnderlyingDS.reset(OpenTile(m_nCurX, m_nCurY, bEmptyContent));
20✔
2615
            if (bEmptyContent)
20✔
2616
            {
2617
                if (!IncrementTileIndices())
15✔
2618
                    return nullptr;
×
2619
                continue;
15✔
2620
            }
2621
            if (m_poUnderlyingDS == nullptr)
5✔
2622
            {
2623
                return nullptr;
×
2624
            }
2625
            m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
5✔
2626
            if (m_poUnderlyingLayer == nullptr)
5✔
2627
            {
2628
                return nullptr;
×
2629
            }
2630
            FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
5✔
2631
        }
2632

2633
        auto poSrcFeature = m_poUnderlyingLayer->GetNextFeature();
5✔
2634
        if (poSrcFeature != nullptr)
5✔
2635
        {
2636
            return BuildFeature(poSrcFeature, m_nCurX, m_nCurY);
5✔
2637
        }
2638

2639
        m_poUnderlyingDS.reset();
×
2640
        m_poUnderlyingLayer = nullptr;
×
2641

2642
        if (!IncrementTileIndices())
×
2643
            return nullptr;
×
2644
    }
15✔
2645
}
2646

2647
/************************************************************************/
2648
/*                           GetFeature()                               */
2649
/************************************************************************/
2650

2651
OGRFeature *OGCAPITiledLayer::GetFeature(GIntBig nFID)
×
2652
{
2653
    if (nFID < 0)
×
2654
        return nullptr;
×
2655
    const GIntBig nFIDInTile =
×
2656
        nFID / (m_oTileMatrix.mMatrixWidth * m_oTileMatrix.mMatrixHeight);
×
2657
    const GIntBig nTileID =
×
2658
        nFID % (m_oTileMatrix.mMatrixWidth * m_oTileMatrix.mMatrixHeight);
×
2659
    const int nY = static_cast<int>(nTileID / m_oTileMatrix.mMatrixWidth);
×
2660
    const int nX = static_cast<int>(nTileID % m_oTileMatrix.mMatrixWidth);
×
2661
    bool bEmptyContent = false;
×
2662
    std::unique_ptr<GDALDataset> poUnderlyingDS(
2663
        OpenTile(nX, nY, bEmptyContent));
×
2664
    if (poUnderlyingDS == nullptr)
×
2665
        return nullptr;
×
2666
    OGRLayer *poUnderlyingLayer = poUnderlyingDS->GetLayer(0);
×
2667
    if (poUnderlyingLayer == nullptr)
×
2668
        return nullptr;
×
2669
    FinalizeFeatureDefnWithLayer(poUnderlyingLayer);
×
2670
    OGRFeature *poSrcFeature = poUnderlyingLayer->GetFeature(nFIDInTile);
×
2671
    if (poSrcFeature == nullptr)
×
2672
        return nullptr;
×
2673
    return BuildFeature(poSrcFeature, nX, nY);
×
2674
}
2675

2676
/************************************************************************/
2677
/*                         EstablishFields()                            */
2678
/************************************************************************/
2679

2680
void OGCAPITiledLayer::EstablishFields()
110✔
2681
{
2682
    if (!m_bFeatureDefnEstablished && !m_bEstablishFieldsCalled)
110✔
2683
    {
2684
        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
        for (int i = 0; i < 10; ++i)
×
2689
        {
2690
            bool bEmptyContent = false;
×
2691
            m_poUnderlyingDS.reset(OpenTile(m_nCurX, m_nCurY, bEmptyContent));
×
2692
            if (bEmptyContent || !m_poUnderlyingDS)
×
2693
            {
2694
                if (!IncrementTileIndices())
×
2695
                    break;
×
2696
                continue;
×
2697
            }
2698
            m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
×
2699
            if (m_poUnderlyingLayer)
×
2700
            {
2701
                FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
×
2702
                break;
×
2703
            }
2704
        }
2705

2706
        if (!m_bFeatureDefnEstablished)
×
2707
        {
2708
            // Try to sample at different locations in the extent
2709
            for (int j = 0; !m_bFeatureDefnEstablished && j < 3; ++j)
×
2710
            {
2711
                m_nCurY = m_nMinY + (2 * j + 1) * (m_nMaxY - m_nMinY) / 6;
×
2712
                for (int i = 0; i < 3; ++i)
×
2713
                {
2714
                    m_nCurX = m_nMinX + (2 * i + 1) * (m_nMaxX - m_nMinX) / 6;
×
2715
                    bool bEmptyContent = false;
×
2716
                    m_poUnderlyingDS.reset(
×
2717
                        OpenTile(m_nCurX, m_nCurY, bEmptyContent));
2718
                    if (bEmptyContent || !m_poUnderlyingDS)
×
2719
                    {
2720
                        continue;
×
2721
                    }
2722
                    m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
×
2723
                    if (m_poUnderlyingLayer)
×
2724
                    {
2725
                        FinalizeFeatureDefnWithLayer(m_poUnderlyingLayer);
×
2726
                        break;
×
2727
                    }
2728
                }
2729
            }
2730
        }
2731

2732
        if (!m_bFeatureDefnEstablished)
×
2733
        {
2734
            CPLDebug("OGCAPI", "Could not establish feature definition. No "
×
2735
                               "valid tile found in sampling done");
2736
        }
2737

2738
        ResetReading();
×
2739
    }
2740
}
110✔
2741

2742
/************************************************************************/
2743
/*                            SetExtent()                               */
2744
/************************************************************************/
2745

2746
void OGCAPITiledLayer::SetExtent(double dfXMin, double dfYMin, double dfXMax,
35✔
2747
                                 double dfYMax)
2748
{
2749
    m_sEnvelope.MinX = dfXMin;
35✔
2750
    m_sEnvelope.MinY = dfYMin;
35✔
2751
    m_sEnvelope.MaxX = dfXMax;
35✔
2752
    m_sEnvelope.MaxY = dfYMax;
35✔
2753
}
35✔
2754

2755
/************************************************************************/
2756
/*                           IGetExtent()                               */
2757
/************************************************************************/
2758

2759
OGRErr OGCAPITiledLayer::IGetExtent(int /* iGeomField */, OGREnvelope *psExtent,
×
2760
                                    bool /* bForce */)
2761
{
2762
    *psExtent = m_sEnvelope;
×
2763
    return OGRERR_NONE;
×
2764
}
2765

2766
/************************************************************************/
2767
/*                         ISetSpatialFilter()                          */
2768
/************************************************************************/
2769

2770
OGRErr OGCAPITiledLayer::ISetSpatialFilter(int iGeomField,
×
2771
                                           const OGRGeometry *poGeomIn)
2772
{
2773
    const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
×
2774
    if (eErr == OGRERR_NONE)
×
2775
    {
2776
        OGREnvelope sEnvelope;
×
2777
        if (m_poFilterGeom != nullptr)
×
2778
            sEnvelope = m_sFilterEnvelope;
×
2779
        else
2780
            sEnvelope = m_sEnvelope;
×
2781

2782
        const double dfTileDim = m_oTileMatrix.mResX * m_oTileMatrix.mTileWidth;
×
2783
        const double dfOriX =
×
2784
            m_bInvertAxis ? m_oTileMatrix.mTopLeftY : m_oTileMatrix.mTopLeftX;
×
2785
        const double dfOriY =
×
2786
            m_bInvertAxis ? m_oTileMatrix.mTopLeftX : m_oTileMatrix.mTopLeftY;
×
2787
        if (sEnvelope.MinX - dfOriX >= -10 * dfTileDim &&
×
2788
            dfOriY - sEnvelope.MinY >= -10 * dfTileDim &&
×
2789
            sEnvelope.MaxX - dfOriX <= 10 * dfTileDim &&
×
2790
            dfOriY - sEnvelope.MaxY <= 10 * dfTileDim)
×
2791
        {
2792
            m_nCurMinX = std::max(
×
2793
                m_nMinX,
×
2794
                static_cast<int>(floor((sEnvelope.MinX - dfOriX) / dfTileDim)));
×
2795
            m_nCurMinY = std::max(
×
2796
                m_nMinY,
×
2797
                static_cast<int>(floor((dfOriY - sEnvelope.MaxY) / dfTileDim)));
×
2798
            m_nCurMaxX = std::min(
×
2799
                m_nMaxX,
×
2800
                static_cast<int>(floor((sEnvelope.MaxX - dfOriX) / dfTileDim)));
×
2801
            m_nCurMaxY = std::min(
×
2802
                m_nMaxY,
×
2803
                static_cast<int>(floor((dfOriY - sEnvelope.MinY) / dfTileDim)));
×
2804
        }
2805
        else
2806
        {
2807
            m_nCurMinX = m_nMinX;
×
2808
            m_nCurMinY = m_nMinY;
×
2809
            m_nCurMaxX = m_nMaxX;
×
2810
            m_nCurMaxY = m_nMaxY;
×
2811
        }
2812

2813
        ResetReading();
×
2814
    }
2815
    return eErr;
×
2816
}
2817

2818
/************************************************************************/
2819
/*                          TestCapability()                            */
2820
/************************************************************************/
2821

2822
int OGCAPITiledLayer::TestCapability(const char *pszCap)
×
2823
{
2824
    if (EQUAL(pszCap, OLCRandomRead))
×
2825
        return true;
×
2826
    if (EQUAL(pszCap, OLCFastGetExtent))
×
2827
        return true;
×
2828
    if (EQUAL(pszCap, OLCStringsAsUTF8))
×
2829
        return true;
×
2830
    if (EQUAL(pszCap, OLCFastSpatialFilter))
×
2831
        return true;
×
2832
    return false;
×
2833
}
2834

2835
/************************************************************************/
2836
/*                            SetMinMaxXY()                             */
2837
/************************************************************************/
2838

2839
void OGCAPITiledLayer::SetMinMaxXY(int minCol, int minRow, int maxCol,
35✔
2840
                                   int maxRow)
2841
{
2842
    m_nMinX = minCol;
35✔
2843
    m_nMinY = minRow;
35✔
2844
    m_nMaxX = maxCol;
35✔
2845
    m_nMaxY = maxRow;
35✔
2846
    m_nCurMinX = m_nMinX;
35✔
2847
    m_nCurMinY = m_nMinY;
35✔
2848
    m_nCurMaxX = m_nMaxX;
35✔
2849
    m_nCurMaxY = m_nMaxY;
35✔
2850
    ResetReading();
35✔
2851
}
35✔
2852

2853
/************************************************************************/
2854
/*                             SetFields()                              */
2855
/************************************************************************/
2856

2857
void OGCAPITiledLayer::SetFields(
×
2858
    const std::vector<std::unique_ptr<OGRFieldDefn>> &apoFields)
2859
{
2860
    m_bFeatureDefnEstablished = true;
×
2861
    for (const auto &poField : apoFields)
×
2862
    {
2863
        m_poFeatureDefn->AddFieldDefn(poField.get());
×
2864
    }
2865
}
×
2866

2867
/************************************************************************/
2868
/*                              Open()                                  */
2869
/************************************************************************/
2870

2871
GDALDataset *OGCAPIDataset::Open(GDALOpenInfo *poOpenInfo)
35✔
2872
{
2873
    if (!Identify(poOpenInfo))
35✔
2874
        return nullptr;
×
2875
    auto poDS = std::make_unique<OGCAPIDataset>();
70✔
2876
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:") ||
35✔
2877
        STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
6✔
2878
        STARTS_WITH(poOpenInfo->pszFilename, "https://"))
×
2879
    {
2880
        if (!poDS->InitFromURL(poOpenInfo))
35✔
2881
            return nullptr;
8✔
2882
    }
2883
    else
2884
    {
2885
        if (!poDS->InitFromFile(poOpenInfo))
×
2886
            return nullptr;
×
2887
    }
2888
    return poDS.release();
27✔
2889
}
2890

2891
/************************************************************************/
2892
/*                        GDALRegister_OGCAPI()                         */
2893
/************************************************************************/
2894

2895
void GDALRegister_OGCAPI()
1,911✔
2896

2897
{
2898
    if (GDALGetDriverByName("OGCAPI") != nullptr)
1,911✔
2899
        return;
282✔
2900

2901
    GDALDriver *poDriver = new GDALDriver();
1,629✔
2902

2903
    poDriver->SetDescription("OGCAPI");
1,629✔
2904
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1,629✔
2905
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1,629✔
2906
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGCAPI");
1,629✔
2907

2908
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,629✔
2909

2910
    poDriver->SetMetadataItem(
1,629✔
2911
        GDAL_DMD_OPENOPTIONLIST,
2912
        "<OpenOptionList>"
2913
        "  <Option name='API' type='string-select' "
2914
        "description='Which API to use to access data' default='AUTO'>"
2915
        "       <Value>AUTO</Value>"
2916
        "       <Value>MAP</Value>"
2917
        "       <Value>TILES</Value>"
2918
        "       <Value>COVERAGE</Value>"
2919
        "       <Value>ITEMS</Value>"
2920
        "  </Option>"
2921
        "  <Option name='IMAGE_FORMAT' scope='raster' type='string-select' "
2922
        "description='Which format to use for pixel acquisition' "
2923
        "default='AUTO'>"
2924
        "       <Value>AUTO</Value>"
2925
        "       <Value>PNG</Value>"
2926
        "       <Value>PNG_PREFERRED</Value>"
2927
        "       <Value>JPEG</Value>"
2928
        "       <Value>JPEG_PREFERRED</Value>"
2929
        "       <Value>GEOTIFF</Value>"
2930
        "  </Option>"
2931
        "  <Option name='VECTOR_FORMAT' scope='vector' type='string-select' "
2932
        "description='Which format to use for vector data acquisition' "
2933
        "default='AUTO'>"
2934
        "       <Value>AUTO</Value>"
2935
        "       <Value>GEOJSON</Value>"
2936
        "       <Value>GEOJSON_PREFERRED</Value>"
2937
        "       <Value>MVT</Value>"
2938
        "       <Value>MVT_PREFERRED</Value>"
2939
        "  </Option>"
2940
        "  <Option name='TILEMATRIXSET' type='string' "
2941
        "description='Identifier of the required tile matrix set'/>"
2942
        "  <Option name='PREFERRED_TILEMATRIXSET' type='string' "
2943
        "description='dentifier of the preferred tile matrix set' "
2944
        "default='WorldCRS84Quad'/>"
2945
        "  <Option name='TILEMATRIX' scope='raster' type='string' "
2946
        "description='Tile matrix identifier.'/>"
2947
        "  <Option name='CACHE' scope='raster' type='boolean' "
2948
        "description='Whether to enable block/tile caching' default='YES'/>"
2949
        "  <Option name='MAX_CONNECTIONS' scope='raster' type='int' "
2950
        "description='Maximum number of connections' default='5'/>"
2951
        "  <Option name='MINX' type='float' "
2952
        "description='Minimum value (in SRS of TileMatrixSet) of X'/>"
2953
        "  <Option name='MINY' type='float' "
2954
        "description='Minimum value (in SRS of TileMatrixSet) of Y'/>"
2955
        "  <Option name='MAXX' type='float' "
2956
        "description='Maximum value (in SRS of TileMatrixSet) of X'/>"
2957
        "  <Option name='MAXY' type='float' "
2958
        "description='Maximum value (in SRS of TileMatrixSet) of Y'/>"
2959
        "</OpenOptionList>");
1,629✔
2960

2961
    poDriver->pfnIdentify = OGCAPIDataset::Identify;
1,629✔
2962
    poDriver->pfnOpen = OGCAPIDataset::Open;
1,629✔
2963

2964
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
2965
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc