Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/oapif/ogroapifdriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OGR
4
 * Purpose:  Implements OGC API - Features (previously known as WFS3)
5
 * Author:   Even Rouault, even dot rouault at spatialys dot com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018-2019, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogrsf_frmts.h"
14
#include "cpl_conv.h"
15
#include "cpl_minixml.h"
16
#include "cpl_http.h"
17
#include "ogr_swq.h"
18
#include "parsexsd.h"
19
20
#include <algorithm>
21
#include <cinttypes>
22
#include <memory>
23
#include <vector>
24
#include <set>
25
26
// g++ -Wshadow -Wextra -std=c++11 -fPIC -g -Wall
27
// ogr/ogrsf_frmts/wfs/ogroapif*.cpp -shared -o ogr_OAPIF.so -Iport -Igcore
28
// -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gml -Iogr/ogrsf_frmts/wfs -L.
29
// -lgdal
30
31
extern "C" void RegisterOGROAPIF();
32
33
0
#define MEDIA_TYPE_OAPI_3_0 "application/vnd.oai.openapi+json;version=3.0"
34
0
#define MEDIA_TYPE_OAPI_3_0_ALT "application/openapi+json;version=3.0"
35
363
#define MEDIA_TYPE_JSON "application/json"
36
0
#define MEDIA_TYPE_GEOJSON "application/geo+json"
37
0
#define MEDIA_TYPE_TEXT_XML "text/xml"
38
0
#define MEDIA_TYPE_APPLICATION_XML "application/xml"
39
0
#define MEDIA_TYPE_JSON_SCHEMA "application/schema+json"
40
41
constexpr const char *OGC_CRS84_WKT =
42
    "GEOGCRS[\"WGS 84 (CRS84)\",ENSEMBLE[\"World Geodetic System 1984 "
43
    "ensemble\",MEMBER[\"World Geodetic System 1984 "
44
    "(Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World "
45
    "Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 "
46
    "(G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World "
47
    "Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 "
48
    "(G2139)\"],ELLIPSOID[\"WGS "
49
    "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]]"
50
    ",PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS["
51
    "ellipsoidal,2],AXIS[\"geodetic longitude "
52
    "(Lon)\",east,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS["
53
    "\"geodetic latitude "
54
    "(Lat)\",north,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE["
55
    "SCOPE[\"Not "
56
    "known.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"OGC\",\"CRS84\"]]";
57
58
/************************************************************************/
59
/*                           OGROAPIFDataset                            */
60
/************************************************************************/
61
class OGROAPIFLayer;
62
63
class OGROAPIFDataset final : public GDALDataset
64
{
65
    friend class OGROAPIFLayer;
66
67
    bool m_bMustCleanPersistent = false;
68
69
    // Server base URL. Like "https://example.com"
70
    // Relative links are relative to it
71
    CPLString m_osServerBaseURL{};
72
73
    // Service base URL. Like "https://example.com/ogcapi"
74
    CPLString m_osRootURL{};
75
76
    CPLString m_osUserQueryParams{};
77
    CPLString m_osUserPwd{};
78
    int m_nPageSize = 1000;
79
    int m_nInitialRequestPageSize = 20;
80
    bool m_bPageSizeSetFromOpenOptions = false;
81
    std::vector<std::unique_ptr<OGRLayer>> m_apoLayers{};
82
    std::string m_osAskedCRS{};
83
    OGRSpatialReference m_oAskedCRS{};
84
    bool m_bAskedCRSIsRequired = false;
85
    bool m_bServerFeaturesAxisOrderGISFriendly = false;
86
87
    bool m_bAPIDocLoaded = false;
88
    CPLJSONDocument m_oAPIDoc{};
89
90
    bool m_bLandingPageDocLoaded = false;
91
    CPLJSONDocument m_oLandingPageDoc{};
92
93
    bool m_bIgnoreSchema = false;
94
95
    std::string m_osDateTime{};
96
97
    bool Download(const CPLString &osURL, const char *pszAccept,
98
                  CPLString &osResult, CPLString &osContentType,
99
                  CPLStringList *paosHeaders = nullptr);
100
101
    bool DownloadJSon(const CPLString &osURL, CPLJSONDocument &oDoc,
102
                      const char *pszAccept = MEDIA_TYPE_GEOJSON
103
                      ", " MEDIA_TYPE_JSON,
104
                      CPLStringList *paosHeaders = nullptr);
105
106
    bool LoadJSONCollection(const CPLJSONObject &oCollection,
107
                            const CPLJSONArray &oGlobalCRSList);
108
    bool LoadJSONCollections(const CPLString &osResultIn,
109
                             const std::string &osCollectionsURL);
110
111
    /**
112
     * Determines the page size by making a call to the API endpoint to get the server's
113
     * default and max limits for the collection items specified by itemsUrl
114
     */
115
    void DeterminePageSizeFromAPI(const std::string &itemsUrl);
116
117
  public:
118
363
    OGROAPIFDataset() = default;
119
    ~OGROAPIFDataset() override;
120
121
    int GetLayerCount() const override
122
0
    {
123
0
        return static_cast<int>(m_apoLayers.size());
124
0
    }
125
126
    const OGRLayer *GetLayer(int idx) const override;
127
128
    bool Open(GDALOpenInfo *);
129
    const CPLJSONDocument &GetAPIDoc(std::string &osURLOut);
130
    const CPLJSONDocument &GetLandingPageDoc(std::string &osURLOut);
131
132
    CPLString ResolveURL(const CPLString &osURL,
133
                         const std::string &osRequestURL) const;
134
};
135
136
/************************************************************************/
137
/*                            OGROAPIFLayer                             */
138
/************************************************************************/
139
140
class OGROAPIFLayer final : public OGRLayer
141
{
142
    OGROAPIFDataset *m_poDS = nullptr;
143
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
144
    bool m_bIsGeographicCRS = false;
145
    bool m_bCRSHasGISFriendlyOrder = false;
146
    bool m_bHasEmittedContentCRSWarning = false;
147
    bool m_bHasEmittedJsonCRWarning = false;
148
    std::string m_osActiveCRS{};
149
    CPLString m_osURL{};
150
    CPLString m_osPath{};
151
    OGREnvelope m_oExtent{};
152
    OGREnvelope m_oOriginalExtent{};
153
    OGRSpatialReference m_oOriginalExtentCRS{};
154
    bool m_bFeatureDefnEstablished = false;
155
    std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
156
    OGRLayer *m_poUnderlyingLayer = nullptr;
157
    GIntBig m_nFID = 1;
158
    CPLString m_osGetURL{};
159
    CPLString m_osAttributeFilter{};
160
    CPLString m_osGetID{};
161
    std::vector<std::string> m_oSupportedCRSList{};
162
    OGRLayer::GetSupportedSRSListRetType m_apoSupportedCRSList{};
163
    bool m_bFilterMustBeClientSideEvaluated = false;
164
    bool m_bGotQueryableAttributes = false;
165
    std::set<CPLString> m_aoSetQueryableAttributes{};
166
    bool m_bHasCQLText = false;
167
    // https://github.com/tschaub/ogcapi-features/blob/json-array-expression/extensions/cql/jfe/readme.md
168
    bool m_bHasJSONFilterExpression = false;
169
    GIntBig m_nTotalFeatureCount = -1;
170
    bool m_bHasIntIdMember = false;
171
    bool m_bHasStringIdMember = false;
172
    std::vector<std::unique_ptr<OGRFieldDefn>> m_apoFieldsFromSchema{};
173
    CPLString m_osDescribedByURL{};
174
    CPLString m_osDescribedByType{};
175
    bool m_bDescribedByIsXML = false;
176
    CPLString m_osQueryablesURL{};
177
    std::vector<std::string> m_aosItemAssetNames{};  // STAC specific
178
    CPLJSONDocument m_oCurDoc{};
179
    int m_iFeatureInPage = 0;
180
181
    void EstablishFeatureDefn();
182
    OGRFeature *GetNextRawFeature();
183
    CPLString AddFilters(const CPLString &osURL);
184
    CPLString BuildFilter(const swq_expr_node *poNode);
185
    CPLString BuildFilterCQLText(const swq_expr_node *poNode);
186
    CPLString BuildFilterJSONFilterExpr(const swq_expr_node *poNode);
187
    bool SupportsResultTypeHits();
188
    void GetQueryableAttributes();
189
    void GetSchema();
190
    void ComputeExtent();
191
192
    CPL_DISALLOW_COPY_ASSIGN(OGROAPIFLayer)
193
194
  public:
195
    OGROAPIFLayer(OGROAPIFDataset *poDS, const CPLString &osName,
196
                  const CPLJSONArray &oBBOX, const std::string &osBBOXCrs,
197
                  std::vector<std::string> &&oCRSList,
198
                  const std::string &osActiveCRS, double dfCoordinateEpoch,
199
                  const CPLJSONArray &oLinks);
200
201
    ~OGROAPIFLayer() override;
202
203
    void SetItemAssets(const CPLJSONObject &oItemAssets);
204
205
    const char *GetName() const override
206
0
    {
207
0
        return GetDescription();
208
0
    }
209
210
    const OGRFeatureDefn *GetLayerDefn() const override;
211
    void ResetReading() override;
212
    OGRFeature *GetNextFeature() override;
213
    OGRFeature *GetFeature(GIntBig) override;
214
    int TestCapability(const char *) const override;
215
    GIntBig GetFeatureCount(int bForce = FALSE) override;
216
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
217
                      bool bForce) override;
218
219
    OGRErr ISetSpatialFilter(int iGeomField,
220
                             const OGRGeometry *poGeom) override;
221
222
    OGRErr SetAttributeFilter(const char *pszQuery) override;
223
224
    const OGRLayer::GetSupportedSRSListRetType &
225
    GetSupportedSRSList(int iGeomField) override;
226
    OGRErr SetActiveSRS(int iGeomField,
227
                        const OGRSpatialReference *poSRS) override;
228
229
    void SetTotalItemCount(GIntBig nCount)
230
0
    {
231
0
        m_nTotalFeatureCount = nCount;
232
0
    }
233
};
234
235
/************************************************************************/
236
/*                          CheckContentType()                          */
237
/************************************************************************/
238
239
// We may ask for "application/openapi+json;version=3.0"
240
// and the server returns "application/openapi+json; charset=utf-8; version=3.0"
241
static bool CheckContentType(const char *pszGotContentType,
242
                             const char *pszExpectedContentType)
243
0
{
244
0
    CPLStringList aosGotTokens(CSLTokenizeString2(pszGotContentType, "; ", 0));
245
0
    CPLStringList aosExpectedTokens(
246
0
        CSLTokenizeString2(pszExpectedContentType, "; ", 0));
247
0
    for (int i = 0; i < aosExpectedTokens.size(); i++)
248
0
    {
249
0
        bool bFound = false;
250
0
        for (int j = 0; j < aosGotTokens.size(); j++)
251
0
        {
252
0
            if (EQUAL(aosExpectedTokens[i], aosGotTokens[j]))
253
0
            {
254
0
                bFound = true;
255
0
                break;
256
0
            }
257
0
        }
258
0
        if (!bFound)
259
0
            return false;
260
0
    }
261
0
    return true;
262
0
}
263
264
/************************************************************************/
265
/*                          ~OGROAPIFDataset()                          */
266
/************************************************************************/
267
268
OGROAPIFDataset::~OGROAPIFDataset()
269
363
{
270
363
    if (m_bMustCleanPersistent)
271
363
    {
272
363
        char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
273
363
                                              CPLSPrintf("OAPIF:%p", this));
274
363
        CPLHTTPDestroyResult(CPLHTTPFetch(m_osRootURL, papszOptions));
275
363
        CSLDestroy(papszOptions);
276
363
    }
277
363
}
278
279
/************************************************************************/
280
/*                             ResolveURL()                             */
281
/************************************************************************/
282
283
// Resolve relative links and re-inject authentication elements.
284
// If source URL is https://user:pwd@server.com/bla
285
// and link only contains https://server.com/bla, then insert
286
// into it user:pwd
287
CPLString OGROAPIFDataset::ResolveURL(const CPLString &osURL,
288
                                      const std::string &osRequestURL) const
289
0
{
290
0
    const auto CleanURL = [](const std::string &osStr)
291
0
    {
292
0
        std::string osRet(osStr);
293
0
        const auto nPos = osRet.rfind('?');
294
0
        if (nPos != std::string::npos)
295
0
            osRet.resize(nPos);
296
0
        if (!osRet.empty() && osRet.back() == '/')
297
0
            osRet.pop_back();
298
0
        return osRet;
299
0
    };
300
301
0
    CPLString osRet(osURL);
302
    // Cf https://datatracker.ietf.org/doc/html/rfc3986#section-5.4
303
    // Partial implementation for usual cases...
304
0
    std::string osRequestURLBase =
305
0
        CPLGetPathSafe(CleanURL(osRequestURL).c_str());
306
0
    if (!osURL.empty() && osURL[0] == '/')
307
0
        osRet = m_osServerBaseURL + osURL;
308
0
    else if (osURL.size() > 2 && osURL[0] == '.' && osURL[1] == '/')
309
0
        osRet = osRequestURLBase + osURL.substr(1);
310
0
    else if (osURL.size() > 3 && osURL[0] == '.' && osURL[1] == '.' &&
311
0
             osURL[2] == '/')
312
0
    {
313
0
        std::string osModifiedRequestURL(std::move(osRequestURLBase));
314
0
        while (osRet.size() > 3 && osRet[0] == '.' && osRet[1] == '.' &&
315
0
               osRet[2] == '/')
316
0
        {
317
0
            osModifiedRequestURL = CPLGetPathSafe(osModifiedRequestURL.c_str());
318
0
            osRet = osRet.substr(3);
319
0
        }
320
0
        osRet = osModifiedRequestURL + "/" + osRet;
321
0
    }
322
0
    else if (!STARTS_WITH(osURL.c_str(), "http://") &&
323
0
             !STARTS_WITH(osURL.c_str(), "https://") &&
324
0
             !STARTS_WITH(osURL.c_str(), "file://"))
325
0
    {
326
0
        osRet = osRequestURLBase + "/" + osURL;
327
0
    }
328
329
0
    const auto nArobaseInURLPos = m_osServerBaseURL.find('@');
330
0
    if (!osRet.empty() && STARTS_WITH(m_osServerBaseURL, "https://") &&
331
0
        STARTS_WITH(osRet, "https://") &&
332
0
        nArobaseInURLPos != std::string::npos &&
333
0
        osRet.find('@') == std::string::npos)
334
0
    {
335
0
        const auto nFirstSlashPos =
336
0
            m_osServerBaseURL.find('/', strlen("https://"));
337
0
        if (nFirstSlashPos == std::string::npos ||
338
0
            nFirstSlashPos > nArobaseInURLPos)
339
0
        {
340
0
            auto osUserPwd = m_osServerBaseURL.substr(
341
0
                strlen("https://"), nArobaseInURLPos - strlen("https://"));
342
0
            std::string osServer(
343
0
                nFirstSlashPos == std::string::npos
344
0
                    ? m_osServerBaseURL.substr(nArobaseInURLPos + 1)
345
0
                    : m_osServerBaseURL.substr(nArobaseInURLPos + 1,
346
0
                                               nFirstSlashPos -
347
0
                                                   nArobaseInURLPos));
348
0
            if (STARTS_WITH(osRet, ("https://" + osServer).c_str()))
349
0
            {
350
0
                osRet = "https://" + osUserPwd + "@" +
351
0
                        osRet.substr(strlen("https://"));
352
0
            }
353
0
        }
354
0
    }
355
0
    return osRet;
356
0
}
357
358
/************************************************************************/
359
/*                              Download()                              */
360
/************************************************************************/
361
362
bool OGROAPIFDataset::Download(const CPLString &osURL, const char *pszAccept,
363
                               CPLString &osResult, CPLString &osContentType,
364
                               CPLStringList *paosHeaders)
365
363
{
366
363
#ifndef REMOVE_HACK
367
363
    VSIStatBufL sStatBuf;
368
363
    if (VSIStatL(osURL, &sStatBuf) == 0)
369
0
    {
370
0
        CPLDebug("OAPIF", "Reading %s", osURL.c_str());
371
0
        GByte *pabyRet = nullptr;
372
0
        if (VSIIngestFile(nullptr, osURL, &pabyRet, nullptr, -1))
373
0
        {
374
0
            osResult = reinterpret_cast<char *>(pabyRet);
375
0
            CPLFree(pabyRet);
376
0
        }
377
0
        return false;
378
0
    }
379
363
#endif
380
363
    char **papszOptions = nullptr;
381
382
363
    if (pszAccept)
383
363
    {
384
363
        papszOptions =
385
363
            CSLSetNameValue(papszOptions, "HEADERS",
386
363
                            (CPLString("Accept: ") + pszAccept).c_str());
387
363
    }
388
389
363
    if (!m_osUserPwd.empty())
390
0
    {
391
0
        papszOptions =
392
0
            CSLSetNameValue(papszOptions, "USERPWD", m_osUserPwd.c_str());
393
0
    }
394
363
    m_bMustCleanPersistent = true;
395
363
    papszOptions =
396
363
        CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=OAPIF:%p", this));
397
363
    CPLString osURLWithQueryParameters(osURL);
398
363
    if (!m_osUserQueryParams.empty() &&
399
225
        osURL.find('?' + m_osUserQueryParams) == std::string::npos &&
400
225
        osURL.find('&' + m_osUserQueryParams) == std::string::npos)
401
225
    {
402
225
        if (osURL.find('?') == std::string::npos)
403
225
        {
404
225
            osURLWithQueryParameters += '?';
405
225
        }
406
0
        else
407
0
        {
408
0
            osURLWithQueryParameters += '&';
409
0
        }
410
225
        osURLWithQueryParameters += m_osUserQueryParams;
411
225
    }
412
363
    CPLHTTPResult *psResult =
413
363
        CPLHTTPFetch(osURLWithQueryParameters, papszOptions);
414
363
    CSLDestroy(papszOptions);
415
363
    if (!psResult)
416
0
        return false;
417
418
363
    if (psResult->pszErrBuf != nullptr)
419
363
    {
420
363
        std::string osErrorMsg(psResult->pszErrBuf);
421
363
        const char *pszData =
422
363
            reinterpret_cast<const char *>(psResult->pabyData);
423
363
        if (pszData)
424
0
        {
425
0
            osErrorMsg += ", ";
426
0
            osErrorMsg.append(pszData, CPLStrnlen(pszData, 1000));
427
0
        }
428
363
        CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
429
363
        CPLHTTPDestroyResult(psResult);
430
363
        return false;
431
363
    }
432
433
0
    if (psResult->pszContentType)
434
0
        osContentType = psResult->pszContentType;
435
436
    // Do not check content type if not specified
437
0
    bool bFoundExpectedContentType = pszAccept ? false : true;
438
439
0
    if (!bFoundExpectedContentType)
440
0
    {
441
0
#ifndef REMOVE_HACK
442
        // cppcheck-suppress nullPointer
443
0
        if (strstr(pszAccept, "json"))
444
0
        {
445
0
            if (strstr(osURL, "raw.githubusercontent.com") &&
446
0
                strstr(osURL, ".json"))
447
0
            {
448
0
                bFoundExpectedContentType = true;
449
0
            }
450
0
            else if (psResult->pszContentType != nullptr &&
451
0
                     (CheckContentType(psResult->pszContentType,
452
0
                                       MEDIA_TYPE_JSON) ||
453
0
                      CheckContentType(psResult->pszContentType,
454
0
                                       MEDIA_TYPE_GEOJSON)))
455
0
            {
456
0
                bFoundExpectedContentType = true;
457
0
            }
458
0
        }
459
0
#endif
460
461
        // cppcheck-suppress nullPointer
462
0
        if (strstr(pszAccept, "xml") && psResult->pszContentType != nullptr &&
463
0
            (CheckContentType(psResult->pszContentType, MEDIA_TYPE_TEXT_XML) ||
464
0
             CheckContentType(psResult->pszContentType,
465
0
                              MEDIA_TYPE_APPLICATION_XML)))
466
0
        {
467
0
            bFoundExpectedContentType = true;
468
0
        }
469
470
        // cppcheck-suppress nullPointer
471
0
        if (strstr(pszAccept, MEDIA_TYPE_JSON_SCHEMA) &&
472
0
            psResult->pszContentType != nullptr &&
473
0
            (CheckContentType(psResult->pszContentType, MEDIA_TYPE_JSON) ||
474
0
             CheckContentType(psResult->pszContentType,
475
0
                              MEDIA_TYPE_JSON_SCHEMA)))
476
0
        {
477
0
            bFoundExpectedContentType = true;
478
0
        }
479
480
0
        for (const char *pszMediaType : {
481
0
                 MEDIA_TYPE_JSON,
482
0
                 MEDIA_TYPE_GEOJSON,
483
0
                 MEDIA_TYPE_OAPI_3_0,
484
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
485
0
                 MEDIA_TYPE_OAPI_3_0_ALT,
486
0
#endif
487
0
             })
488
0
        {
489
            // cppcheck-suppress nullPointer
490
0
            if (strstr(pszAccept, pszMediaType) &&
491
0
                psResult->pszContentType != nullptr &&
492
0
                CheckContentType(psResult->pszContentType, pszMediaType))
493
0
            {
494
0
                bFoundExpectedContentType = true;
495
0
                break;
496
0
            }
497
0
        }
498
0
    }
499
500
0
    if (!bFoundExpectedContentType)
501
0
    {
502
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected Content-Type: %s",
503
0
                 psResult->pszContentType ? psResult->pszContentType
504
0
                                          : "(null)");
505
0
        CPLHTTPDestroyResult(psResult);
506
0
        return false;
507
0
    }
508
509
0
    if (psResult->pabyData == nullptr)
510
0
    {
511
0
        CPLError(CE_Failure, CPLE_AppDefined,
512
0
                 "Empty content returned by server");
513
0
        CPLHTTPDestroyResult(psResult);
514
0
        return false;
515
0
    }
516
517
0
    if (paosHeaders)
518
0
    {
519
0
        *paosHeaders = CSLDuplicate(psResult->papszHeaders);
520
0
    }
521
522
0
    osResult = reinterpret_cast<const char *>(psResult->pabyData);
523
0
    CPLHTTPDestroyResult(psResult);
524
0
    return true;
525
0
}
526
527
/************************************************************************/
528
/*                            DownloadJSon()                            */
529
/************************************************************************/
530
531
bool OGROAPIFDataset::DownloadJSon(const CPLString &osURL,
532
                                   CPLJSONDocument &oDoc, const char *pszAccept,
533
                                   CPLStringList *paosHeaders)
534
0
{
535
0
    CPLString osResult;
536
0
    CPLString osContentType;
537
0
    if (!Download(osURL, pszAccept, osResult, osContentType, paosHeaders))
538
0
        return false;
539
0
    return oDoc.LoadMemory(osResult);
540
0
}
541
542
/************************************************************************/
543
/*                         GetLandingPageDoc()                          */
544
/************************************************************************/
545
546
const CPLJSONDocument &OGROAPIFDataset::GetLandingPageDoc(std::string &osURLOut)
547
0
{
548
0
    if (m_bLandingPageDocLoaded)
549
0
        return m_oLandingPageDoc;
550
0
    m_bLandingPageDocLoaded = true;
551
0
    osURLOut = m_osRootURL;
552
0
    CPL_IGNORE_RET_VAL(
553
0
        DownloadJSon(osURLOut, m_oLandingPageDoc, MEDIA_TYPE_JSON));
554
0
    return m_oLandingPageDoc;
555
0
}
556
557
/************************************************************************/
558
/*                             GetAPIDoc()                              */
559
/************************************************************************/
560
561
const CPLJSONDocument &OGROAPIFDataset::GetAPIDoc(std::string &osURLOut)
562
0
{
563
0
    if (m_bAPIDocLoaded)
564
0
        return m_oAPIDoc;
565
0
    m_bAPIDocLoaded = true;
566
567
    // Fetch the /api URL from the links of the landing page
568
0
    CPLString osAPIURL;
569
0
    std::string osLandingPageURL;
570
0
    const auto &oLandingPage = GetLandingPageDoc(osLandingPageURL);
571
0
    if (oLandingPage.GetRoot().IsValid())
572
0
    {
573
0
        const auto oLinks = oLandingPage.GetRoot().GetArray("links");
574
0
        if (oLinks.IsValid())
575
0
        {
576
0
            int nCountRelAPI = 0;
577
0
            for (int i = 0; i < oLinks.Size(); i++)
578
0
            {
579
0
                CPLJSONObject oLink = oLinks[i];
580
0
                if (!oLink.IsValid() ||
581
0
                    oLink.GetType() != CPLJSONObject::Type::Object)
582
0
                {
583
0
                    continue;
584
0
                }
585
0
                const auto osRel(oLink.GetString("rel"));
586
0
                const auto osType(oLink.GetString("type"));
587
0
                if (EQUAL(osRel.c_str(), "service-desc")
588
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
589
                    // Needed for http://beta.fmi.fi/data/3/wfs/sofp
590
0
                    || EQUAL(osRel.c_str(), "service")
591
0
#endif
592
0
                )
593
0
                {
594
0
                    nCountRelAPI++;
595
0
                    osAPIURL =
596
0
                        ResolveURL(oLink.GetString("href"), osLandingPageURL);
597
0
                    if (osType == MEDIA_TYPE_OAPI_3_0
598
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
599
                        // Needed for http://beta.fmi.fi/data/3/wfs/sofp
600
0
                        || osType == MEDIA_TYPE_OAPI_3_0_ALT
601
0
#endif
602
0
                    )
603
0
                    {
604
0
                        nCountRelAPI = 1;
605
0
                        break;
606
0
                    }
607
0
                }
608
0
            }
609
0
            if (!osAPIURL.empty() && nCountRelAPI > 1)
610
0
            {
611
0
                osAPIURL.clear();
612
0
            }
613
0
        }
614
0
    }
615
616
0
    const char *pszAccept = MEDIA_TYPE_OAPI_3_0
617
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
618
0
        ", " MEDIA_TYPE_OAPI_3_0_ALT ", " MEDIA_TYPE_JSON
619
0
#endif
620
0
        ;
621
622
0
    if (!osAPIURL.empty())
623
0
    {
624
0
        osURLOut = osAPIURL;
625
0
        CPL_IGNORE_RET_VAL(DownloadJSon(osAPIURL, m_oAPIDoc, pszAccept));
626
0
        return m_oAPIDoc;
627
0
    }
628
629
0
#ifndef REMOVE_HACK
630
0
    CPLPushErrorHandler(CPLQuietErrorHandler);
631
0
    CPLString osURL(m_osRootURL + "/api");
632
0
    osURL = CPLGetConfigOption("OGR_WFS3_API_URL", osURL.c_str());
633
0
    bool bOK = DownloadJSon(osURL, m_oAPIDoc, pszAccept);
634
0
    CPLPopErrorHandler();
635
0
    CPLErrorReset();
636
0
    if (bOK)
637
0
    {
638
0
        return m_oAPIDoc;
639
0
    }
640
641
0
    osURLOut = m_osRootURL + "/api/";
642
0
    if (DownloadJSon(osURLOut, m_oAPIDoc, pszAccept))
643
0
    {
644
0
        return m_oAPIDoc;
645
0
    }
646
0
#endif
647
0
    return m_oAPIDoc;
648
0
}
649
650
/************************************************************************/
651
/*                         LoadJSONCollection()                         */
652
/************************************************************************/
653
654
bool OGROAPIFDataset::LoadJSONCollection(const CPLJSONObject &oCollection,
655
                                         const CPLJSONArray &oGlobalCRSList)
656
0
{
657
0
    if (oCollection.GetType() != CPLJSONObject::Type::Object)
658
0
        return false;
659
660
    // As used by https://maps.ecere.com/ogcapi/collections?f=json
661
0
    const auto osLayerDataType = oCollection.GetString("layerDataType");
662
0
    if (osLayerDataType == "Raster" || osLayerDataType == "Coverage")
663
0
        return false;
664
665
0
    CPLString osName(oCollection.GetString("id"));
666
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
667
0
    if (osName.empty())
668
0
        osName = oCollection.GetString("name");
669
0
    if (osName.empty())
670
0
        osName = oCollection.GetString("collectionId");
671
0
#endif
672
0
    if (osName.empty())
673
0
        return false;
674
675
0
    CPLString osTitle(oCollection.GetString("title"));
676
0
    CPLString osDescription(oCollection.GetString("description"));
677
0
    CPLJSONArray oBBOX = oCollection.GetArray("extent/spatial/bbox");
678
0
#ifndef REMOVE_HACK_FOR_NLS_FINLAND_SERVICES
679
0
    if (!oBBOX.IsValid())
680
0
        oBBOX = oCollection.GetArray("extent/spatialExtent/bbox");
681
0
#endif
682
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
683
0
    if (!oBBOX.IsValid())
684
0
        oBBOX = oCollection.GetArray("extent/spatial");
685
0
#endif
686
0
    const std::string osBBOXCrs = oCollection.GetString("extent/spatial/crs");
687
688
    // Deal with CRS list
689
0
    const CPLJSONArray oCRSListOri = oCollection.GetArray("crs");
690
0
    std::vector<std::string> oCRSList;
691
0
    std::string osActiveCRS;
692
0
    double dfCoordinateEpoch = 0.0;
693
0
    if (oCRSListOri.IsValid())
694
0
    {
695
0
        std::set<std::string> oSetCRS;
696
0
        for (const auto &oCRS : oCRSListOri)
697
0
        {
698
0
            if (oCRS.ToString() == "#/crs")
699
0
            {
700
0
                if (!oGlobalCRSList.IsValid())
701
0
                {
702
0
                    CPLError(CE_Failure, CPLE_AppDefined,
703
0
                             "Collection %s refer to #/crs global CRS list, "
704
0
                             "which is missing",
705
0
                             osTitle.c_str());
706
0
                }
707
0
                else
708
0
                {
709
0
                    for (const auto &oGlobalCRS : oGlobalCRSList)
710
0
                    {
711
0
                        std::string osCRS = oGlobalCRS.ToString();
712
0
                        if (oSetCRS.find(osCRS) == oSetCRS.end())
713
0
                        {
714
0
                            oSetCRS.insert(osCRS);
715
0
                            oCRSList.push_back(std::move(osCRS));
716
0
                        }
717
0
                    }
718
0
                }
719
0
            }
720
0
            else
721
0
            {
722
0
                std::string osCRS = oCRS.ToString();
723
0
                if (oSetCRS.find(osCRS) == oSetCRS.end())
724
0
                {
725
0
                    oSetCRS.insert(osCRS);
726
0
                    oCRSList.push_back(std::move(osCRS));
727
0
                }
728
0
            }
729
0
        }
730
731
0
        if (!m_oAskedCRS.IsEmpty())
732
0
        {
733
0
            for (const auto &osCRS : oCRSList)
734
0
            {
735
0
                OGRSpatialReference oSRS;
736
0
                if (oSRS.SetFromUserInput(
737
0
                        osCRS.c_str(),
738
0
                        OGRSpatialReference::
739
0
                            SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
740
0
                    OGRERR_NONE)
741
0
                {
742
0
                    if (oSRS.IsSame(&m_oAskedCRS))
743
0
                    {
744
0
                        osActiveCRS = osCRS;
745
0
                        break;
746
0
                    }
747
0
                }
748
0
            }
749
0
            if (osActiveCRS.empty())
750
0
            {
751
0
                if (m_bAskedCRSIsRequired)
752
0
                {
753
0
                    std::string osList;
754
0
                    for (const auto &osCRS : oCRSList)
755
0
                    {
756
0
                        if (!osList.empty())
757
0
                            osList += ", ";
758
0
                        if (osCRS.find(
759
0
                                "http://www.opengis.net/def/crs/EPSG/0/") == 0)
760
0
                            osList +=
761
0
                                "EPSG:" +
762
0
                                osCRS.substr(strlen(
763
0
                                    "http://www.opengis.net/def/crs/EPSG/0/"));
764
0
                        else if (osCRS.find("https://www.opengis.net/def/crs/"
765
0
                                            "EPSG/0/") == 0)
766
0
                            osList +=
767
0
                                "EPSG:" +
768
0
                                osCRS.substr(strlen(
769
0
                                    "https://www.opengis.net/def/crs/EPSG/0/"));
770
0
                        else if (osCRS.find("http://www.opengis.net/def/crs/"
771
0
                                            "OGC/1.3/") == 0)
772
0
                            osList +=
773
0
                                "OGC:" +
774
0
                                osCRS.substr(strlen(
775
0
                                    "http://www.opengis.net/def/crs/OGC/1.3/"));
776
0
                        else if (osCRS.find("https://www.opengis.net/def/crs/"
777
0
                                            "OGC/1.3/") == 0)
778
0
                            osList += "OGC:" + osCRS.substr(strlen(
779
0
                                                   "https://www.opengis.net/"
780
0
                                                   "def/crs/OGC/1.3/"));
781
0
                        else
782
0
                            osList += osCRS;
783
0
                    }
784
0
                    CPLError(CE_Failure, CPLE_AppDefined,
785
0
                             "CRS %s not found in list of CRS valid for "
786
0
                             "collection %s. Available CRS are %s.",
787
0
                             m_osAskedCRS.c_str(), osTitle.c_str(),
788
0
                             osList.c_str());
789
0
                    return false;
790
0
                }
791
0
                else
792
0
                {
793
0
                    CPLDebug("OAPIF",
794
0
                             "CRS %s not found in list of CRS valid for "
795
0
                             "collection %s",
796
0
                             m_osAskedCRS.c_str(), osTitle.c_str());
797
0
                }
798
0
            }
799
0
        }
800
0
    }
801
802
    // storageCRS is in the "OGC API - Features - Part 2: Coordinate Reference
803
    // Systems" extension
804
0
    std::string osStorageCRS = oCollection.GetString("storageCrs");
805
0
    const double dfStorageCrsCoordinateEpoch =
806
0
        oCollection.GetDouble("storageCrsCoordinateEpoch");
807
0
    if (osActiveCRS.empty() || osActiveCRS == osStorageCRS)
808
0
    {
809
0
        osActiveCRS = std::move(osStorageCRS);
810
0
        dfCoordinateEpoch = dfStorageCrsCoordinateEpoch;
811
0
    }
812
813
0
    const auto oLinks = oCollection.GetArray("links");
814
0
    auto poLayer = std::make_unique<OGROAPIFLayer>(
815
0
        this, osName, oBBOX, osBBOXCrs, std::move(oCRSList), osActiveCRS,
816
0
        dfCoordinateEpoch, oLinks);
817
0
    if (!osTitle.empty())
818
0
        poLayer->SetMetadataItem("TITLE", osTitle.c_str());
819
0
    if (!osDescription.empty())
820
0
        poLayer->SetMetadataItem("DESCRIPTION", osDescription.c_str());
821
0
    auto oTemporalInterval = oCollection.GetArray("extent/temporal/interval");
822
0
    if (oTemporalInterval.IsValid() && oTemporalInterval.Size() == 1 &&
823
0
        oTemporalInterval[0].GetType() == CPLJSONObject::Type::Array)
824
0
    {
825
0
        auto oArray = oTemporalInterval[0].ToArray();
826
0
        if (oArray.Size() == 2)
827
0
        {
828
0
            if (oArray[0].GetType() == CPLJSONObject::Type::String)
829
0
            {
830
0
                poLayer->SetMetadataItem("TEMPORAL_INTERVAL_MIN",
831
0
                                         oArray[0].ToString().c_str());
832
0
            }
833
0
            if (oArray[1].GetType() == CPLJSONObject::Type::String)
834
0
            {
835
0
                poLayer->SetMetadataItem("TEMPORAL_INTERVAL_MAX",
836
0
                                         oArray[1].ToString().c_str());
837
0
            }
838
0
        }
839
0
    }
840
841
    // STAC specific
842
0
    auto oItemAssets = oCollection.GetObj("item_assets");
843
0
    if (oItemAssets.IsValid() &&
844
0
        oItemAssets.GetType() == CPLJSONObject::Type::Object)
845
0
    {
846
0
        poLayer->SetItemAssets(oItemAssets);
847
0
    }
848
849
    // LDProxy extension (https://github.com/opengeospatial/ogcapi-features/issues/261#issuecomment-1271010859)
850
0
    const auto nItemCount = oCollection.GetLong("itemCount", -1);
851
0
    if (nItemCount >= 0)
852
0
        poLayer->SetTotalItemCount(nItemCount);
853
854
0
    auto oJSONStr = oCollection.Format(CPLJSONObject::PrettyFormat::Pretty);
855
0
    char *apszMetadata[2] = {&oJSONStr[0], nullptr};
856
0
    poLayer->SetMetadata(apszMetadata, "json:metadata");
857
858
0
    m_apoLayers.emplace_back(std::move(poLayer));
859
0
    return true;
860
0
}
861
862
/************************************************************************/
863
/*                        LoadJSONCollections()                         */
864
/************************************************************************/
865
866
bool OGROAPIFDataset::LoadJSONCollections(const CPLString &osResultIn,
867
                                          const std::string &osCollectionsURL)
868
0
{
869
0
    std::string osParentURL(osCollectionsURL);
870
0
    CPLString osResult(osResultIn);
871
0
    while (!osResult.empty())
872
0
    {
873
0
        CPLJSONDocument oDoc;
874
0
        if (!oDoc.LoadMemory(osResult))
875
0
        {
876
0
            return false;
877
0
        }
878
0
        const auto &oRoot = oDoc.GetRoot();
879
0
        CPLJSONArray oCollections = oRoot.GetArray("collections");
880
0
        if (!oCollections.IsValid())
881
0
        {
882
0
            CPLError(CE_Failure, CPLE_AppDefined, "No collections array");
883
0
            return false;
884
0
        }
885
886
0
        const auto oGlobalCRSList = oRoot.GetArray("crs");
887
888
0
        for (int i = 0; i < oCollections.Size(); i++)
889
0
        {
890
0
            LoadJSONCollection(oCollections[i], oGlobalCRSList);
891
0
        }
892
893
0
        osResult.clear();
894
895
        // Paging is a (unspecified) extension to the core used by
896
        // https://{api_key}:@api.planet.com/analytics
897
0
        const auto oLinks = oRoot.GetArray("links");
898
0
        if (oLinks.IsValid())
899
0
        {
900
0
            CPLString osNextURL;
901
0
            int nCountRelNext = 0;
902
0
            for (int i = 0; i < oLinks.Size(); i++)
903
0
            {
904
0
                CPLJSONObject oLink = oLinks[i];
905
0
                if (!oLink.IsValid() ||
906
0
                    oLink.GetType() != CPLJSONObject::Type::Object)
907
0
                {
908
0
                    continue;
909
0
                }
910
0
                if (EQUAL(oLink.GetString("rel").c_str(), "next"))
911
0
                {
912
0
                    osNextURL = oLink.GetString("href");
913
0
                    nCountRelNext++;
914
0
                    auto type = oLink.GetString("type");
915
0
                    if (type == MEDIA_TYPE_GEOJSON || type == MEDIA_TYPE_JSON)
916
0
                    {
917
0
                        nCountRelNext = 1;
918
0
                        break;
919
0
                    }
920
0
                }
921
0
            }
922
0
            if (nCountRelNext == 1 && !osNextURL.empty())
923
0
            {
924
0
                CPLString osContentType;
925
0
                osNextURL = ResolveURL(osNextURL, osParentURL);
926
0
                osParentURL = osNextURL;
927
0
                if (!Download(osNextURL, MEDIA_TYPE_JSON, osResult,
928
0
                              osContentType))
929
0
                {
930
0
                    return false;
931
0
                }
932
0
            }
933
0
        }
934
0
    }
935
0
    return !m_apoLayers.empty();
936
0
}
937
938
void OGROAPIFDataset::DeterminePageSizeFromAPI(const std::string &itemsUrl)
939
0
{
940
    // Try to get max limit from api
941
0
    int nMaximum{-1};
942
0
    int nDefault{-1};
943
    // Not sure if min should be considered
944
    //int nMinimum { -1 };
945
0
    std::string osAPIURL;
946
0
    const CPLJSONDocument &oDoc{GetAPIDoc(osAPIURL)};
947
0
    const auto &oRoot = oDoc.GetRoot();
948
949
0
    bool bFound{false};
950
951
    // limit from api document
952
0
    if (oRoot.IsValid())
953
0
    {
954
955
0
        const auto paths{oRoot.GetObj("paths")};
956
957
0
        if (paths.IsValid())
958
0
        {
959
960
0
            const auto pathName{itemsUrl.substr(m_osRootURL.length())};
961
0
            const auto path{paths.GetObj(pathName)};
962
963
0
            if (path.IsValid())
964
0
            {
965
966
0
                const auto parameters{path.GetArray("get/parameters")};
967
968
                // check $ref
969
0
                for (const auto &param : parameters)
970
0
                {
971
0
                    const auto ref{param.GetString("$ref")};
972
0
                    if (ref.find("limit") != std::string::npos)
973
0
                    {
974
                        // Examine ref
975
0
                        if (ref.find("http") == 0 &&
976
0
                            ref.find(".yml") == std::string::npos &&
977
0
                            ref.find(".yaml") ==
978
0
                                std::string::
979
0
                                    npos)  // Remote document, skip yaml
980
0
                        {
981
                            // Only reinject auth if the URL matches
982
0
                            auto limitUrl{ref.find(m_osRootURL) == 0
983
0
                                              ? ResolveURL(ref, osAPIURL)
984
0
                                              : ref};
985
0
                            std::string fragment;
986
0
                            const auto hashPos{limitUrl.find('#')};
987
0
                            if (hashPos != std::string::npos)
988
0
                            {
989
                                // Remove leading #
990
0
                                fragment = limitUrl.substr(hashPos + 1);
991
0
                                limitUrl = limitUrl.substr(0, hashPos);
992
0
                            }
993
0
                            CPLString osResult;
994
0
                            CPLString osContentType;
995
                            // Do not limit accepted content-types, external resources may have any
996
0
                            if (!Download(limitUrl, nullptr, osResult,
997
0
                                          osContentType))
998
0
                            {
999
0
                                CPLDebug("OAPIF",
1000
0
                                         "Could not download OPENAPI $ref: %s",
1001
0
                                         ref.c_str());
1002
0
                                return;
1003
0
                            }
1004
1005
                            // We cannot trust the content-type, try JSON (YAML not implemented)
1006
1007
                            // Try JSON
1008
0
                            CPLJSONDocument oLimitDoc;
1009
0
                            if (oLimitDoc.LoadMemory(osResult))
1010
0
                            {
1011
0
                                const auto oLimitRoot{oLimitDoc.GetRoot()};
1012
0
                                if (oLimitRoot.IsValid())
1013
0
                                {
1014
0
                                    const auto oLimit{
1015
0
                                        oLimitRoot.GetObj(fragment)};
1016
0
                                    if (oLimit.IsValid())
1017
0
                                    {
1018
0
                                        nMaximum = oLimit.GetInteger(
1019
0
                                            "schema/maximum", -1);
1020
                                        //nMinimum = oLimit.GetInteger( "schema/minimum", -1 );
1021
0
                                        nDefault = oLimit.GetInteger(
1022
0
                                            "schema/default", -1);
1023
0
                                        bFound = true;
1024
0
                                    }
1025
0
                                }
1026
0
                            }
1027
0
                        }
1028
0
                        else if (ref.find('#') == 0)  // Local ref
1029
0
                        {
1030
0
                            const auto oLimit{oRoot.GetObj(ref.substr(1))};
1031
0
                            if (oLimit.IsValid())
1032
0
                            {
1033
0
                                nMaximum =
1034
0
                                    oLimit.GetInteger("schema/maximum", -1);
1035
                                //nMinimum = oLimit.GetInteger( "schema/minimum", -1 );
1036
0
                                nDefault =
1037
0
                                    oLimit.GetInteger("schema/default", -1);
1038
0
                                bFound = true;
1039
0
                            }
1040
0
                        }
1041
0
                        else
1042
0
                        {
1043
0
                            CPLDebug("OAPIF", "Could not open OPENAPI $ref: %s",
1044
0
                                     ref.c_str());
1045
0
                        }
1046
0
                    }
1047
0
                }
1048
0
            }
1049
0
        }
1050
0
    }
1051
1052
0
    if (bFound)
1053
0
    {
1054
        // Initially set to GDAL's default (1000)
1055
0
        int pageSize{m_nPageSize};
1056
0
        if (nDefault > 0 && nMaximum > 0)
1057
0
        {
1058
            // Use the default, but if it is below GDAL's default (1000), aim for 1000
1059
            // but clamp to the maximum limit
1060
0
            pageSize = std::min(std::max(pageSize, nDefault), nMaximum);
1061
0
        }
1062
0
        else if (nDefault > 0)
1063
0
            pageSize = std::max(pageSize, nDefault);
1064
0
        else if (nMaximum > 0)
1065
0
            pageSize = nMaximum;
1066
1067
0
        if (m_nPageSize != pageSize)
1068
0
        {
1069
0
            CPLDebug("OAPIF", "Page size set from OPENAPI schema: %d",
1070
0
                     pageSize);
1071
0
            m_nPageSize = pageSize;
1072
0
        }
1073
0
    }
1074
0
}
1075
1076
/************************************************************************/
1077
/*                        ConcatenateURLParts()                         */
1078
/************************************************************************/
1079
1080
static std::string ConcatenateURLParts(const std::string &osPart1,
1081
                                       const std::string &osPart2)
1082
363
{
1083
363
    if (!osPart1.empty() && osPart1.back() == '/' && !osPart2.empty() &&
1084
3
        osPart2.front() == '/')
1085
3
    {
1086
3
        return osPart1.substr(0, osPart1.size() - 1) + osPart2;
1087
3
    }
1088
360
    return osPart1 + osPart2;
1089
363
}
1090
1091
/************************************************************************/
1092
/*                                Open()                                */
1093
/************************************************************************/
1094
1095
bool OGROAPIFDataset::Open(GDALOpenInfo *poOpenInfo)
1096
363
{
1097
363
    CPLString osCollectionDescURL;
1098
1099
363
    m_osRootURL = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "URL",
1100
363
                                       poOpenInfo->pszFilename);
1101
363
    if (STARTS_WITH_CI(m_osRootURL, "WFS3:"))
1102
186
        m_osRootURL = m_osRootURL.substr(strlen("WFS3:"));
1103
177
    else if (STARTS_WITH_CI(m_osRootURL, "OAPIF:"))
1104
177
        m_osRootURL = m_osRootURL.substr(strlen("OAPIF:"));
1105
0
    else if (STARTS_WITH_CI(m_osRootURL, "OAPIF_COLLECTION:"))
1106
0
    {
1107
        // Used by the OGCAPI driver
1108
0
        osCollectionDescURL = m_osRootURL.substr(strlen("OAPIF_COLLECTION:"));
1109
0
        m_osRootURL = osCollectionDescURL;
1110
0
    }
1111
1112
363
    const auto nPosQuestionMark = m_osRootURL.find('?');
1113
363
    if (nPosQuestionMark != std::string::npos)
1114
225
    {
1115
225
        m_osUserQueryParams = m_osRootURL.substr(nPosQuestionMark + 1);
1116
225
        m_osRootURL.resize(nPosQuestionMark);
1117
225
    }
1118
1119
363
    const auto nCollectionsPos = m_osRootURL.find("/collections/");
1120
363
    if (nCollectionsPos != std::string::npos)
1121
0
    {
1122
0
        if (osCollectionDescURL.empty())
1123
0
            osCollectionDescURL = m_osRootURL;
1124
0
        m_osRootURL.resize(nCollectionsPos);
1125
0
    }
1126
1127
    // m_osServerBaseURL is just the "https://example.com" part from
1128
    // "https://example.com/foo/bar"
1129
363
    m_osServerBaseURL = m_osRootURL;
1130
363
    {
1131
363
        const char *pszStr = m_osServerBaseURL.c_str();
1132
363
        const char *pszPtr = pszStr;
1133
363
        if (STARTS_WITH(pszPtr, "http://"))
1134
0
            pszPtr += strlen("http://");
1135
363
        else if (STARTS_WITH(pszPtr, "https://"))
1136
0
            pszPtr += strlen("https://");
1137
363
        pszPtr = strchr(pszPtr, '/');
1138
363
        if (pszPtr)
1139
346
            m_osServerBaseURL.assign(pszStr, pszPtr - pszStr);
1140
363
    }
1141
1142
363
    m_bIgnoreSchema = CPLTestBool(CSLFetchNameValueDef(
1143
363
        poOpenInfo->papszOpenOptions, "IGNORE_SCHEMA", "FALSE"));
1144
1145
363
    const int pageSize = atoi(
1146
363
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "PAGE_SIZE", "-1"));
1147
1148
363
    if (pageSize > 0)
1149
0
    {
1150
0
        m_nPageSize = pageSize;
1151
0
        m_bPageSizeSetFromOpenOptions = true;
1152
0
    }
1153
1154
363
    m_osDateTime =
1155
363
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "DATETIME", "");
1156
1157
363
    const int initialRequestPageSize = atoi(CSLFetchNameValueDef(
1158
363
        poOpenInfo->papszOpenOptions, "INITIAL_REQUEST_PAGE_SIZE", "-1"));
1159
1160
363
    if (initialRequestPageSize >= 1)
1161
0
    {
1162
0
        m_nInitialRequestPageSize = initialRequestPageSize;
1163
0
    }
1164
1165
363
    m_osUserPwd =
1166
363
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "USERPWD", "");
1167
363
    std::string osCRS =
1168
363
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CRS", "");
1169
363
    std::string osPreferredCRS =
1170
363
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "PREFERRED_CRS", "");
1171
363
    if (!osCRS.empty())
1172
0
    {
1173
0
        if (!osPreferredCRS.empty())
1174
0
        {
1175
0
            CPLError(
1176
0
                CE_Failure, CPLE_AppDefined,
1177
0
                "CRS and PREFERRED_CRS open options are mutually exclusive.");
1178
0
            return false;
1179
0
        }
1180
0
        m_osAskedCRS = osCRS;
1181
0
        if (m_oAskedCRS.SetFromUserInput(
1182
0
                osCRS.c_str(),
1183
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
1184
0
            OGRERR_NONE)
1185
0
        {
1186
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for CRS");
1187
0
            return false;
1188
0
        }
1189
0
        m_bAskedCRSIsRequired = true;
1190
0
    }
1191
363
    else if (!osPreferredCRS.empty())
1192
0
    {
1193
0
        m_osAskedCRS = osPreferredCRS;
1194
0
        if (m_oAskedCRS.SetFromUserInput(
1195
0
                osPreferredCRS.c_str(),
1196
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
1197
0
            OGRERR_NONE)
1198
0
        {
1199
0
            CPLError(CE_Failure, CPLE_AppDefined,
1200
0
                     "Invalid value for PREFERRED_CRS");
1201
0
            return false;
1202
0
        }
1203
0
    }
1204
1205
363
    m_bServerFeaturesAxisOrderGISFriendly =
1206
363
        EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
1207
363
                                   "SERVER_FEATURE_AXIS_ORDER",
1208
363
                                   "AUTHORITY_COMPLIANT"),
1209
363
              "GIS_FRIENDLY");
1210
1211
363
    CPLString osResult;
1212
363
    CPLString osContentType;
1213
1214
363
    if (!osCollectionDescURL.empty())
1215
0
    {
1216
0
        if (!Download(osCollectionDescURL, MEDIA_TYPE_JSON, osResult,
1217
0
                      osContentType))
1218
0
        {
1219
0
            return false;
1220
0
        }
1221
0
        CPLJSONDocument oDoc;
1222
0
        if (!oDoc.LoadMemory(osResult))
1223
0
        {
1224
0
            return false;
1225
0
        }
1226
0
        const auto &oRoot = oDoc.GetRoot();
1227
0
        return LoadJSONCollection(oRoot, CPLJSONArray());
1228
0
    }
1229
1230
363
    const std::string osCollectionsURL(
1231
363
        ConcatenateURLParts(m_osRootURL, "/collections"));
1232
363
    if (!Download(osCollectionsURL, MEDIA_TYPE_JSON, osResult, osContentType))
1233
363
    {
1234
363
        return false;
1235
363
    }
1236
1237
0
    if (osContentType.find("json") != std::string::npos)
1238
0
    {
1239
0
        return LoadJSONCollections(osResult, osCollectionsURL);
1240
0
    }
1241
1242
0
    return true;
1243
0
}
1244
1245
/************************************************************************/
1246
/*                              GetLayer()                              */
1247
/************************************************************************/
1248
1249
const OGRLayer *OGROAPIFDataset::GetLayer(int nIndex) const
1250
0
{
1251
0
    if (nIndex < 0 || nIndex >= GetLayerCount())
1252
0
        return nullptr;
1253
0
    return m_apoLayers[nIndex].get();
1254
0
}
1255
1256
/************************************************************************/
1257
/*                              Identify()                              */
1258
/************************************************************************/
1259
1260
static int OGROAPIFDriverIdentify(GDALOpenInfo *poOpenInfo)
1261
1262
96.6k
{
1263
96.6k
    return STARTS_WITH_CI(poOpenInfo->pszFilename, "WFS3:") ||
1264
96.6k
           STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF:") ||
1265
96.6k
           STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF_COLLECTION:") ||
1266
95.9k
           (poOpenInfo->IsSingleAllowedDriver("OAPIF") &&
1267
0
            (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
1268
0
             STARTS_WITH(poOpenInfo->pszFilename, "https://")));
1269
96.6k
}
1270
1271
/************************************************************************/
1272
/*                      HasGISFriendlyAxisOrder()                       */
1273
/************************************************************************/
1274
1275
static bool HasGISFriendlyAxisOrder(const OGRSpatialReference *poSRS)
1276
0
{
1277
0
    const auto &axisMapping = poSRS->GetDataAxisToSRSAxisMapping();
1278
0
    return axisMapping.size() >= 2 && axisMapping[0] == 1 &&
1279
0
           axisMapping[1] == 2;
1280
0
}
1281
1282
/************************************************************************/
1283
/*                           OGROAPIFLayer()                            */
1284
/************************************************************************/
1285
1286
OGROAPIFLayer::OGROAPIFLayer(OGROAPIFDataset *poDS, const CPLString &osName,
1287
                             const CPLJSONArray &oBBOX,
1288
                             const std::string &osBBOXCrs,
1289
                             std::vector<std::string> &&oCRSList,
1290
                             const std::string &osActiveCRS,
1291
                             double dfCoordinateEpoch,
1292
                             const CPLJSONArray &oLinks)
1293
0
    : m_poDS(poDS)
1294
0
{
1295
0
    m_poFeatureDefn = new OGRFeatureDefn(osName);
1296
0
    m_poFeatureDefn->Reference();
1297
0
    SetDescription(osName);
1298
0
    m_oSupportedCRSList = std::move(oCRSList);
1299
1300
0
    OGRSpatialReference *poSRS = new OGRSpatialReference();
1301
0
    poSRS->SetFromUserInput(
1302
0
        !osActiveCRS.empty() ? osActiveCRS.c_str() : SRS_WKT_WGS84_LAT_LONG,
1303
0
        OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
1304
0
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1305
0
    m_bIsGeographicCRS = CPL_TO_BOOL(poSRS->IsGeographic());
1306
0
    m_bCRSHasGISFriendlyOrder =
1307
0
        osActiveCRS.empty() || HasGISFriendlyAxisOrder(poSRS);
1308
0
    m_osActiveCRS = osActiveCRS;
1309
0
    if (dfCoordinateEpoch > 0)
1310
0
        poSRS->SetCoordinateEpoch(dfCoordinateEpoch);
1311
0
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
1312
1313
0
    poSRS->Release();
1314
1315
0
    if (oBBOX.IsValid() && oBBOX.Size() > 0)
1316
0
    {
1317
0
        CPLJSONArray oRealBBOX;
1318
        // In the final 1.0.0 spec, spatial.bbox is an array (normally with
1319
        // a single element) of 4-element arrays
1320
0
        if (oBBOX[0].GetType() == CPLJSONObject::Type::Array)
1321
0
        {
1322
0
            oRealBBOX = oBBOX[0].ToArray();
1323
0
        }
1324
0
#ifndef REMOVE_SUPPORT_FOR_OLD_VERSIONS
1325
0
        else if (oBBOX.Size() == 4 || oBBOX.Size() == 6)
1326
0
        {
1327
0
            oRealBBOX = oBBOX;
1328
0
        }
1329
0
#endif
1330
0
        if (oRealBBOX.Size() == 4 || oRealBBOX.Size() == 6)
1331
0
        {
1332
0
            m_oOriginalExtent.MinX = oRealBBOX[0].ToDouble();
1333
0
            m_oOriginalExtent.MinY = oRealBBOX[1].ToDouble();
1334
0
            m_oOriginalExtent.MaxX =
1335
0
                oRealBBOX[oRealBBOX.Size() == 6 ? 3 : 2].ToDouble();
1336
0
            m_oOriginalExtent.MaxY =
1337
0
                oRealBBOX[oRealBBOX.Size() == 6 ? 4 : 3].ToDouble();
1338
1339
0
            m_oOriginalExtentCRS.SetFromUserInput(
1340
0
                !osBBOXCrs.empty() ? osBBOXCrs.c_str() : OGC_CRS84_WKT,
1341
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
1342
1343
            // Handle bbox over antimeridian, which we do not support properly
1344
            // in OGR
1345
0
            if (m_oOriginalExtentCRS.IsGeographic())
1346
0
            {
1347
0
                const bool bSwitchXY =
1348
0
                    !HasGISFriendlyAxisOrder(&m_oOriginalExtentCRS);
1349
0
                if (bSwitchXY)
1350
0
                {
1351
0
                    std::swap(m_oOriginalExtent.MinX, m_oOriginalExtent.MinY);
1352
0
                    std::swap(m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY);
1353
0
                }
1354
1355
0
                if (m_oOriginalExtent.MinX > m_oOriginalExtent.MaxX &&
1356
0
                    fabs(m_oOriginalExtent.MinX) <= 180.0 &&
1357
0
                    fabs(m_oOriginalExtent.MaxX) <= 180.0)
1358
0
                {
1359
0
                    m_oOriginalExtent.MinX = -180.0;
1360
0
                    m_oOriginalExtent.MaxX = 180.0;
1361
0
                }
1362
1363
0
                if (bSwitchXY)
1364
0
                {
1365
0
                    std::swap(m_oOriginalExtent.MinX, m_oOriginalExtent.MinY);
1366
0
                    std::swap(m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY);
1367
0
                }
1368
0
            }
1369
0
        }
1370
0
    }
1371
1372
    // Default to what the spec mandates for the /items URL, but check links
1373
    // later
1374
0
    m_osURL = ConcatenateURLParts(m_poDS->m_osRootURL,
1375
0
                                  "/collections/" + osName + "/items");
1376
0
    const std::string osParentURL(m_osURL);
1377
0
    m_osPath = "/collections/" + osName + "/items";
1378
1379
0
    if (oLinks.IsValid())
1380
0
    {
1381
0
        for (int i = 0; i < oLinks.Size(); i++)
1382
0
        {
1383
0
            CPLJSONObject oLink = oLinks[i];
1384
0
            if (!oLink.IsValid() ||
1385
0
                oLink.GetType() != CPLJSONObject::Type::Object)
1386
0
            {
1387
0
                continue;
1388
0
            }
1389
0
            const auto osRel(oLink.GetString("rel"));
1390
0
            const auto osURL = oLink.GetString("href");
1391
0
            const auto type = oLink.GetString("type");
1392
0
            if (EQUAL(osRel.c_str(), "describedby"))
1393
0
            {
1394
0
                if (type == MEDIA_TYPE_TEXT_XML ||
1395
0
                    type == MEDIA_TYPE_APPLICATION_XML)
1396
0
                {
1397
0
                    m_osDescribedByURL = osURL;
1398
0
                    m_osDescribedByType = type;
1399
0
                    m_bDescribedByIsXML = true;
1400
0
                }
1401
0
                else if (type == MEDIA_TYPE_JSON_SCHEMA &&
1402
0
                         m_osDescribedByURL.empty())
1403
0
                {
1404
0
                    m_osDescribedByURL = osURL;
1405
0
                    m_osDescribedByType = type;
1406
0
                    m_bDescribedByIsXML = false;
1407
0
                }
1408
0
            }
1409
0
            else if (osRel == "queryables" ||  // deprecated, prior to Part-3
1410
0
                     osRel ==
1411
0
                         "http://www.opengis.net/def/rel/ogc/1.0/queryables" ||
1412
0
                     osRel == "[ogc-rel:queryables]")
1413
0
            {
1414
0
                if (type == MEDIA_TYPE_JSON ||  // deprecated, prior to Part-3
1415
0
                    type == MEDIA_TYPE_JSON_SCHEMA || m_osQueryablesURL.empty())
1416
0
                {
1417
0
                    m_osQueryablesURL = m_poDS->ResolveURL(osURL, osParentURL);
1418
0
                }
1419
0
            }
1420
0
            else if (EQUAL(osRel.c_str(), "items"))
1421
0
            {
1422
0
                if (type == MEDIA_TYPE_GEOJSON)
1423
0
                {
1424
0
                    m_osURL = m_poDS->ResolveURL(osURL, osParentURL);
1425
0
                }
1426
0
            }
1427
0
        }
1428
0
        if (!m_osDescribedByURL.empty())
1429
0
        {
1430
0
            m_osDescribedByURL =
1431
0
                m_poDS->ResolveURL(m_osDescribedByURL, osParentURL);
1432
0
        }
1433
0
    }
1434
1435
0
    OGROAPIFLayer::ResetReading();
1436
0
}
1437
1438
/************************************************************************/
1439
/*                           ~OGROAPIFLayer()                           */
1440
/************************************************************************/
1441
1442
OGROAPIFLayer::~OGROAPIFLayer()
1443
0
{
1444
0
    m_poFeatureDefn->Release();
1445
0
}
1446
1447
/************************************************************************/
1448
/*                        GetSupportedSRSList()                         */
1449
/************************************************************************/
1450
1451
const OGRLayer::GetSupportedSRSListRetType &
1452
OGROAPIFLayer::GetSupportedSRSList(int /*iGeomField*/)
1453
0
{
1454
0
    if (!m_oSupportedCRSList.empty() && m_apoSupportedCRSList.empty())
1455
0
    {
1456
0
        for (const auto &osCRS : m_oSupportedCRSList)
1457
0
        {
1458
0
            auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
1459
0
            if (poSRS->SetFromUserInput(
1460
0
                    osCRS.c_str(),
1461
0
                    OGRSpatialReference::
1462
0
                        SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
1463
0
            {
1464
0
                m_apoSupportedCRSList.emplace_back(std::move(poSRS));
1465
0
            }
1466
0
        }
1467
0
    }
1468
0
    return m_apoSupportedCRSList;
1469
0
}
1470
1471
/************************************************************************/
1472
/*                            SetActiveSRS()                            */
1473
/************************************************************************/
1474
1475
OGRErr OGROAPIFLayer::SetActiveSRS(int /*iGeomField*/,
1476
                                   const OGRSpatialReference *poSRS)
1477
0
{
1478
0
    if (poSRS == nullptr)
1479
0
        return OGRERR_FAILURE;
1480
0
    const char *const apszOptions[] = {
1481
0
        "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
1482
0
    for (const auto &osCRS : m_oSupportedCRSList)
1483
0
    {
1484
0
        OGRSpatialReference oTmpSRS;
1485
0
        if (oTmpSRS.SetFromUserInput(
1486
0
                osCRS.c_str(),
1487
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1488
0
                OGRERR_NONE &&
1489
0
            oTmpSRS.IsSame(poSRS, apszOptions))
1490
0
        {
1491
0
            m_osActiveCRS = osCRS;
1492
0
            auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(0);
1493
0
            if (poGeomFieldDefn)
1494
0
            {
1495
0
                OGRSpatialReference *poSRSClone = poSRS->Clone();
1496
0
                poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1497
0
                poGeomFieldDefn->SetSpatialRef(poSRSClone);
1498
0
                m_bIsGeographicCRS = CPL_TO_BOOL(poSRSClone->IsGeographic());
1499
0
                m_bCRSHasGISFriendlyOrder = HasGISFriendlyAxisOrder(poSRSClone);
1500
0
                poSRSClone->Release();
1501
0
            }
1502
0
            m_oExtent = OGREnvelope();
1503
0
            SetSpatialFilter(nullptr);
1504
0
            ResetReading();
1505
0
            return OGRERR_NONE;
1506
0
        }
1507
0
    }
1508
0
    return OGRERR_FAILURE;
1509
0
}
1510
1511
/************************************************************************/
1512
/*                           ComputeExtent()                            */
1513
/************************************************************************/
1514
1515
void OGROAPIFLayer::ComputeExtent()
1516
0
{
1517
0
    m_oExtent = m_oOriginalExtent;
1518
0
    const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(0);
1519
0
    if (poGeomFieldDefn)
1520
0
    {
1521
0
        const OGRSpatialReference *poSRS = poGeomFieldDefn->GetSpatialRef();
1522
0
        if (poSRS && !poSRS->IsSame(&m_oOriginalExtentCRS))
1523
0
        {
1524
0
            auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1525
0
                OGRCreateCoordinateTransformation(&m_oOriginalExtentCRS,
1526
0
                                                  poSRS));
1527
0
            if (poCT)
1528
0
            {
1529
0
                poCT->TransformBounds(
1530
0
                    m_oOriginalExtent.MinX, m_oOriginalExtent.MinY,
1531
0
                    m_oOriginalExtent.MaxX, m_oOriginalExtent.MaxY,
1532
0
                    &m_oExtent.MinX, &m_oExtent.MinY, &m_oExtent.MaxX,
1533
0
                    &m_oExtent.MaxY, 20);
1534
0
            }
1535
0
        }
1536
0
    }
1537
0
}
1538
1539
/************************************************************************/
1540
/*                           SetItemAssets()                            */
1541
/************************************************************************/
1542
1543
void OGROAPIFLayer::SetItemAssets(const CPLJSONObject &oItemAssets)
1544
0
{
1545
0
    auto oChildren = oItemAssets.GetChildren();
1546
0
    for (const auto &oItemAsset : oChildren)
1547
0
    {
1548
0
        m_aosItemAssetNames.emplace_back(oItemAsset.GetName());
1549
0
    }
1550
0
}
1551
1552
/************************************************************************/
1553
/*                            ResolveRefs()                             */
1554
/************************************************************************/
1555
1556
static CPLJSONObject ResolveRefs(const CPLJSONObject &oRoot,
1557
                                 const CPLJSONObject &oObj)
1558
0
{
1559
0
    const auto osRef = oObj.GetString("$ref");
1560
0
    if (osRef.empty())
1561
0
        return oObj;
1562
0
    if (STARTS_WITH(osRef.c_str(), "#/"))
1563
0
    {
1564
0
        return oRoot.GetObj(osRef.c_str() + 2);
1565
0
    }
1566
0
    CPLJSONObject oInvalid;
1567
0
    oInvalid.Deinit();
1568
0
    return oInvalid;
1569
0
}
1570
1571
/************************************************************************/
1572
/*                      BuildExampleRecursively()                       */
1573
/************************************************************************/
1574
1575
static bool BuildExampleRecursively(CPLJSONObject &oRes,
1576
                                    const CPLJSONObject &oRoot,
1577
                                    const CPLJSONObject &oObjIn)
1578
0
{
1579
0
    auto oResolvedObj = ResolveRefs(oRoot, oObjIn);
1580
0
    if (!oResolvedObj.IsValid())
1581
0
        return false;
1582
0
    const auto osType = oResolvedObj.GetString("type");
1583
0
    if (osType == "object")
1584
0
    {
1585
0
        const auto oAllOf = oResolvedObj.GetArray("allOf");
1586
0
        const auto oProperties = oResolvedObj.GetObj("properties");
1587
0
        if (oAllOf.IsValid())
1588
0
        {
1589
0
            for (int i = 0; i < oAllOf.Size(); i++)
1590
0
            {
1591
0
                CPLJSONObject oChildRes;
1592
0
                if (BuildExampleRecursively(oChildRes, oRoot, oAllOf[i]) &&
1593
0
                    oChildRes.GetType() == CPLJSONObject::Type::Object)
1594
0
                {
1595
0
                    auto oChildren = oChildRes.GetChildren();
1596
0
                    for (const auto &oChild : oChildren)
1597
0
                    {
1598
0
                        oRes.Add(oChild.GetName(), oChild);
1599
0
                    }
1600
0
                }
1601
0
            }
1602
0
        }
1603
0
        else if (oProperties.IsValid())
1604
0
        {
1605
0
            auto oChildren = oProperties.GetChildren();
1606
0
            for (const auto &oChild : oChildren)
1607
0
            {
1608
0
                CPLJSONObject oChildRes;
1609
0
                if (BuildExampleRecursively(oChildRes, oRoot, oChild))
1610
0
                {
1611
0
                    oRes.Add(oChild.GetName(), oChildRes);
1612
0
                }
1613
0
                else
1614
0
                {
1615
0
                    oRes.Add(oChild.GetName(), "unknown type");
1616
0
                }
1617
0
            }
1618
0
        }
1619
0
        return true;
1620
0
    }
1621
0
    else if (osType == "array")
1622
0
    {
1623
0
        CPLJSONArray oArray;
1624
0
        const auto oItems = oResolvedObj.GetObj("items");
1625
0
        if (oItems.IsValid())
1626
0
        {
1627
0
            CPLJSONObject oChildRes;
1628
0
            if (BuildExampleRecursively(oChildRes, oRoot, oItems))
1629
0
            {
1630
0
                oArray.Add(oChildRes);
1631
0
            }
1632
0
        }
1633
0
        oRes = std::move(oArray);
1634
0
        return true;
1635
0
    }
1636
0
    else if (osType == "string")
1637
0
    {
1638
0
        CPLJSONObject oTemp;
1639
0
        const auto osFormat = oResolvedObj.GetString("format");
1640
0
        if (!osFormat.empty())
1641
0
            oTemp.Set("_", osFormat);
1642
0
        else
1643
0
            oTemp.Set("_", "string");
1644
0
        oRes = oTemp.GetObj("_");
1645
0
        return true;
1646
0
    }
1647
0
    else if (osType == "number")
1648
0
    {
1649
0
        CPLJSONObject oTemp;
1650
0
        oTemp.Set("_", 1.25);
1651
0
        oRes = oTemp.GetObj("_");
1652
0
        return true;
1653
0
    }
1654
0
    else if (osType == "integer")
1655
0
    {
1656
0
        CPLJSONObject oTemp;
1657
0
        oTemp.Set("_", 1);
1658
0
        oRes = oTemp.GetObj("_");
1659
0
        return true;
1660
0
    }
1661
0
    else if (osType == "boolean")
1662
0
    {
1663
0
        CPLJSONObject oTemp;
1664
0
        oTemp.Set("_", true);
1665
0
        oRes = oTemp.GetObj("_");
1666
0
        return true;
1667
0
    }
1668
0
    else if (osType == "null")
1669
0
    {
1670
0
        CPLJSONObject oTemp;
1671
0
        oTemp.SetNull("_");
1672
0
        oRes = oTemp.GetObj("_");
1673
0
        return true;
1674
0
    }
1675
1676
0
    return false;
1677
0
}
1678
1679
/************************************************************************/
1680
/*                     GetObjectExampleFromSchema()                     */
1681
/************************************************************************/
1682
1683
static CPLJSONObject GetObjectExampleFromSchema(const std::string &osJSONSchema)
1684
0
{
1685
0
    CPLJSONDocument oDoc;
1686
0
    if (!oDoc.LoadMemory(osJSONSchema))
1687
0
    {
1688
0
        CPLJSONObject oInvalid;
1689
0
        oInvalid.Deinit();
1690
0
        return oInvalid;
1691
0
    }
1692
0
    const auto &oRoot = oDoc.GetRoot();
1693
0
    CPLJSONObject oRes;
1694
0
    BuildExampleRecursively(oRes, oRoot, oRoot);
1695
0
    return oRes;
1696
0
}
1697
1698
/************************************************************************/
1699
/*                             GetSchema()                              */
1700
/************************************************************************/
1701
1702
void OGROAPIFLayer::GetSchema()
1703
0
{
1704
0
    if (m_osDescribedByURL.empty() || m_poDS->m_bIgnoreSchema)
1705
0
        return;
1706
1707
0
    CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1708
1709
0
    if (m_bDescribedByIsXML)
1710
0
    {
1711
0
        std::vector<GMLFeatureClass *> apoClasses;
1712
0
        bool bFullyUnderstood = false;
1713
0
        bool bUseSchemaImports = false;
1714
0
        bool bHaveSchema = GMLParseXSD(m_osDescribedByURL, bUseSchemaImports,
1715
0
                                       apoClasses, bFullyUnderstood);
1716
0
        if (bHaveSchema && apoClasses.size() == 1)
1717
0
        {
1718
0
            CPLDebug("OAPIF", "Using XML schema");
1719
0
            auto poGMLFeatureClass = apoClasses[0];
1720
0
            if (poGMLFeatureClass->GetGeometryPropertyCount() == 1)
1721
0
            {
1722
                // Force linear type as we work with GeoJSON data
1723
0
                m_poFeatureDefn->SetGeomType(
1724
0
                    OGR_GT_GetLinear(static_cast<OGRwkbGeometryType>(
1725
0
                        poGMLFeatureClass->GetGeometryProperty(0)->GetType())));
1726
0
            }
1727
1728
0
            const int nPropertyCount = poGMLFeatureClass->GetPropertyCount();
1729
            // This is a hack for
1730
            // http://www.pvretano.com/cubewerx/cubeserv/default/wfs/3.0.0/framework/collections/UNINCORPORATED_PL/schema
1731
            // The GML representation has attributes starting all with
1732
            // "UNINCORPORATED_PL." whereas the GeoJSON output not
1733
0
            CPLString osPropertyNamePrefix(GetName());
1734
0
            osPropertyNamePrefix += '.';
1735
0
            bool bAllPrefixed = true;
1736
0
            for (int iField = 0; iField < nPropertyCount; iField++)
1737
0
            {
1738
0
                const auto poProperty = poGMLFeatureClass->GetProperty(iField);
1739
0
                if (!STARTS_WITH(poProperty->GetName(),
1740
0
                                 osPropertyNamePrefix.c_str()))
1741
0
                {
1742
0
                    bAllPrefixed = false;
1743
0
                }
1744
0
            }
1745
0
            for (int iField = 0; iField < nPropertyCount; iField++)
1746
0
            {
1747
0
                const auto poProperty = poGMLFeatureClass->GetProperty(iField);
1748
0
                OGRFieldSubType eSubType = OFSTNone;
1749
0
                const OGRFieldType eFType =
1750
0
                    GML_GetOGRFieldType(poProperty->GetType(), eSubType);
1751
1752
0
                const char *pszName =
1753
0
                    poProperty->GetName() +
1754
0
                    (bAllPrefixed ? osPropertyNamePrefix.size() : 0);
1755
0
                auto poField = std::make_unique<OGRFieldDefn>(pszName, eFType);
1756
0
                poField->SetSubType(eSubType);
1757
0
                m_apoFieldsFromSchema.emplace_back(std::move(poField));
1758
0
            }
1759
0
        }
1760
1761
0
        for (auto poFeatureClass : apoClasses)
1762
0
            delete poFeatureClass;
1763
0
    }
1764
0
    else
1765
0
    {
1766
0
        CPLString osContentType;
1767
0
        CPLString osResult;
1768
0
        if (!m_poDS->Download(m_osDescribedByURL, m_osDescribedByType, osResult,
1769
0
                              osContentType))
1770
0
        {
1771
0
            CPLDebug("OAPIF", "Could not download schema");
1772
0
        }
1773
0
        else
1774
0
        {
1775
0
            const auto oExample = GetObjectExampleFromSchema(osResult);
1776
            // CPLDebug("OAPIF", "Example from schema: %s",
1777
            //          oExample.Format(CPLJSONObject::PrettyFormat::Pretty).c_str());
1778
0
            if (oExample.IsValid() &&
1779
0
                oExample.GetType() == CPLJSONObject::Type::Object)
1780
0
            {
1781
0
                const auto oProperties = oExample.GetObj("properties");
1782
0
                if (oProperties.IsValid() &&
1783
0
                    oProperties.GetType() == CPLJSONObject::Type::Object)
1784
0
                {
1785
0
                    CPLDebug("OAPIF", "Using JSON schema");
1786
0
                    const auto oProps = oProperties.GetChildren();
1787
0
                    for (const auto &oProp : oProps)
1788
0
                    {
1789
0
                        OGRFieldType eType = OFTString;
1790
0
                        OGRFieldSubType eSubType = OFSTNone;
1791
0
                        const auto oType = oProp.GetType();
1792
0
                        if (oType == CPLJSONObject::Type::String)
1793
0
                        {
1794
0
                            if (oProp.ToString() == "date-time")
1795
0
                            {
1796
0
                                eType = OFTDateTime;
1797
0
                            }
1798
0
                            else if (oProp.ToString() == "date")
1799
0
                            {
1800
0
                                eType = OFTDate;
1801
0
                            }
1802
0
                        }
1803
0
                        else if (oType == CPLJSONObject::Type::Boolean)
1804
0
                        {
1805
0
                            eType = OFTInteger;
1806
0
                            eSubType = OFSTBoolean;
1807
0
                        }
1808
0
                        else if (oType == CPLJSONObject::Type::Double)
1809
0
                        {
1810
0
                            eType = OFTReal;
1811
0
                        }
1812
0
                        else if (oType == CPLJSONObject::Type::Integer)
1813
0
                        {
1814
0
                            eType = OFTInteger;
1815
0
                        }
1816
0
                        else if (oType == CPLJSONObject::Type::Long)
1817
0
                        {
1818
0
                            eType = OFTInteger64;
1819
0
                        }
1820
0
                        else if (oType == CPLJSONObject::Type::Array)
1821
0
                        {
1822
0
                            const auto oArray = oProp.ToArray();
1823
0
                            if (oArray.Size() > 0)
1824
0
                            {
1825
0
                                if (oArray[0].GetType() ==
1826
0
                                    CPLJSONObject::Type::String)
1827
0
                                    eType = OFTStringList;
1828
0
                                else if (oArray[0].GetType() ==
1829
0
                                         CPLJSONObject::Type::Integer)
1830
0
                                    eType = OFTIntegerList;
1831
0
                            }
1832
0
                        }
1833
1834
0
                        auto poField = std::make_unique<OGRFieldDefn>(
1835
0
                            oProp.GetName().c_str(), eType);
1836
0
                        poField->SetSubType(eSubType);
1837
0
                        m_apoFieldsFromSchema.emplace_back(std::move(poField));
1838
0
                    }
1839
0
                }
1840
0
            }
1841
0
        }
1842
0
    }
1843
0
}
1844
1845
/************************************************************************/
1846
/*                            GetLayerDefn()                            */
1847
/************************************************************************/
1848
1849
const OGRFeatureDefn *OGROAPIFLayer::GetLayerDefn() const
1850
0
{
1851
0
    if (!m_bFeatureDefnEstablished)
1852
0
        const_cast<OGROAPIFLayer *>(this)->EstablishFeatureDefn();
1853
0
    return m_poFeatureDefn;
1854
0
}
1855
1856
/************************************************************************/
1857
/*                        EstablishFeatureDefn()                        */
1858
/************************************************************************/
1859
1860
void OGROAPIFLayer::EstablishFeatureDefn()
1861
0
{
1862
0
    CPLAssert(!m_bFeatureDefnEstablished);
1863
0
    m_bFeatureDefnEstablished = true;
1864
1865
0
    GetSchema();
1866
1867
0
    if (!m_poDS->m_bPageSizeSetFromOpenOptions)
1868
0
    {
1869
0
        const int nOldPageSize{m_poDS->m_nPageSize};
1870
0
        m_poDS->DeterminePageSizeFromAPI(m_osURL);
1871
        // cppcheck-suppress knownConditionTrueFalse
1872
0
        if (nOldPageSize != m_poDS->m_nPageSize)
1873
0
        {
1874
0
            m_osGetURL = CPLURLAddKVP(m_osGetURL, "limit",
1875
0
                                      CPLSPrintf("%d", m_poDS->m_nPageSize));
1876
0
        }
1877
0
    }
1878
1879
0
    CPLJSONDocument oDoc;
1880
0
    CPLString osURL(m_osURL);
1881
1882
0
    osURL = CPLURLAddKVP(
1883
0
        osURL, "limit",
1884
0
        CPLSPrintf("%d", std::min(m_poDS->m_nInitialRequestPageSize,
1885
0
                                  m_poDS->m_nPageSize)));
1886
0
    if (!m_poDS->DownloadJSon(osURL, oDoc))
1887
0
        return;
1888
1889
0
    const CPLString osTmpFilename(VSIMemGenerateHiddenFilename("oapif.json"));
1890
0
    oDoc.Save(osTmpFilename);
1891
0
    std::unique_ptr<GDALDataset> poDS(GDALDataset::FromHandle(
1892
0
        GDALOpenEx(osTmpFilename, GDAL_OF_VECTOR | GDAL_OF_INTERNAL, nullptr,
1893
0
                   nullptr, nullptr)));
1894
0
    VSIUnlink(osTmpFilename);
1895
0
    if (!poDS.get())
1896
0
        return;
1897
0
    OGRLayer *poLayer = poDS->GetLayer(0);
1898
0
    if (!poLayer)
1899
0
        return;
1900
0
    OGRFeatureDefn *poFeatureDefn = poLayer->GetLayerDefn();
1901
0
    if (m_poFeatureDefn->GetGeomType() == wkbUnknown)
1902
0
    {
1903
0
        m_poFeatureDefn->SetGeomType(poFeatureDefn->GetGeomType());
1904
0
    }
1905
0
    if (m_apoFieldsFromSchema.empty())
1906
0
    {
1907
0
        for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
1908
0
        {
1909
0
            m_poFeatureDefn->AddFieldDefn(poFeatureDefn->GetFieldDefn(i));
1910
0
        }
1911
0
    }
1912
0
    else
1913
0
    {
1914
0
        if (poFeatureDefn->GetFieldCount() > 0 &&
1915
0
            strcmp(poFeatureDefn->GetFieldDefn(0)->GetNameRef(), "id") == 0)
1916
0
        {
1917
0
            m_poFeatureDefn->AddFieldDefn(poFeatureDefn->GetFieldDefn(0));
1918
0
        }
1919
0
        for (const auto &poField : m_apoFieldsFromSchema)
1920
0
        {
1921
0
            m_poFeatureDefn->AddFieldDefn(poField.get());
1922
0
        }
1923
        // In case there would be properties found in sample, but not in
1924
        // schema...
1925
0
        for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
1926
0
        {
1927
0
            auto poFDefn = poFeatureDefn->GetFieldDefn(i);
1928
0
            if (m_poFeatureDefn->GetFieldIndex(poFDefn->GetNameRef()) < 0)
1929
0
            {
1930
0
                m_poFeatureDefn->AddFieldDefn(poFDefn);
1931
0
            }
1932
0
        }
1933
0
    }
1934
1935
0
    for (const auto &osItemAsset : m_aosItemAssetNames)
1936
0
    {
1937
0
        OGRFieldDefn oFieldDefn(("asset_" + osItemAsset + "_href").c_str(),
1938
0
                                OFTString);
1939
        // cppcheck-suppress danglingTemporaryLifetime
1940
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
1941
0
    }
1942
1943
0
    const auto &oRoot = oDoc.GetRoot();
1944
0
    GIntBig nFeatures = oRoot.GetLong("numberMatched", -1);
1945
0
    if (nFeatures >= 0)
1946
0
    {
1947
0
        m_nTotalFeatureCount = nFeatures;
1948
0
    }
1949
1950
0
    auto oFeatures = oRoot.GetArray("features");
1951
0
    if (oFeatures.IsValid() && oFeatures.Size() > 0)
1952
0
    {
1953
0
        auto eType = oFeatures[0].GetObj("id").GetType();
1954
0
        if (eType == CPLJSONObject::Type::Integer ||
1955
0
            eType == CPLJSONObject::Type::Long)
1956
0
        {
1957
0
            m_bHasIntIdMember = true;
1958
0
        }
1959
0
        else if (eType == CPLJSONObject::Type::String)
1960
0
        {
1961
0
            m_bHasStringIdMember = true;
1962
0
        }
1963
0
    }
1964
0
}
1965
1966
/************************************************************************/
1967
/*                            ResetReading()                            */
1968
/************************************************************************/
1969
1970
void OGROAPIFLayer::ResetReading()
1971
0
{
1972
0
    m_poUnderlyingDS.reset();
1973
0
    m_poUnderlyingLayer = nullptr;
1974
0
    m_nFID = 1;
1975
0
    m_osGetURL = m_osURL;
1976
0
    if (!m_osGetID.empty())
1977
0
    {
1978
0
        m_osGetURL += "/" + m_osGetID;
1979
0
    }
1980
0
    else
1981
0
    {
1982
0
        if (m_poDS->m_nPageSize > 0)
1983
0
        {
1984
0
            m_osGetURL = CPLURLAddKVP(m_osGetURL, "limit",
1985
0
                                      CPLSPrintf("%d", m_poDS->m_nPageSize));
1986
0
        }
1987
0
        m_osGetURL = AddFilters(m_osGetURL);
1988
0
    }
1989
0
    m_oCurDoc = CPLJSONDocument();
1990
0
    m_iFeatureInPage = 0;
1991
0
}
1992
1993
/************************************************************************/
1994
/*                             AddFilters()                             */
1995
/************************************************************************/
1996
1997
CPLString OGROAPIFLayer::AddFilters(const CPLString &osURL)
1998
0
{
1999
0
    CPLString osURLNew(osURL);
2000
0
    if (m_poFilterGeom)
2001
0
    {
2002
0
        double dfMinX = m_sFilterEnvelope.MinX;
2003
0
        double dfMinY = m_sFilterEnvelope.MinY;
2004
0
        double dfMaxX = m_sFilterEnvelope.MaxX;
2005
0
        double dfMaxY = m_sFilterEnvelope.MaxY;
2006
0
        bool bAddBBoxFilter = true;
2007
0
        if (m_bIsGeographicCRS)
2008
0
        {
2009
0
            dfMinX = std::max(dfMinX, -180.0);
2010
0
            dfMinY = std::max(dfMinY, -90.0);
2011
0
            dfMaxX = std::min(dfMaxX, 180.0);
2012
0
            dfMaxY = std::min(dfMaxY, 90.0);
2013
0
            bAddBBoxFilter = dfMinX > -180.0 || dfMinY > -90.0 ||
2014
0
                             dfMaxX < 180.0 || dfMaxY < 90.0;
2015
0
        }
2016
0
        if (bAddBBoxFilter)
2017
0
        {
2018
0
            if (!m_bCRSHasGISFriendlyOrder)
2019
0
            {
2020
0
                std::swap(dfMinX, dfMinY);
2021
0
                std::swap(dfMaxX, dfMaxY);
2022
0
            }
2023
0
            osURLNew = CPLURLAddKVP(osURLNew, "bbox",
2024
0
                                    CPLSPrintf("%.17g,%.17g,%.17g,%.17g",
2025
0
                                               dfMinX, dfMinY, dfMaxX, dfMaxY));
2026
0
            if (!m_osActiveCRS.empty())
2027
0
            {
2028
0
                osURLNew =
2029
0
                    CPLURLAddKVP(osURLNew, "bbox-crs", m_osActiveCRS.c_str());
2030
0
            }
2031
0
        }
2032
0
    }
2033
0
    if (!m_osActiveCRS.empty())
2034
0
    {
2035
0
        osURLNew = CPLURLAddKVP(osURLNew, "crs", m_osActiveCRS.c_str());
2036
0
    }
2037
0
    if (!m_osAttributeFilter.empty())
2038
0
    {
2039
0
        if (osURLNew.find('?') == std::string::npos)
2040
0
            osURLNew += "?";
2041
0
        else
2042
0
            osURLNew += "&";
2043
0
        osURLNew += m_osAttributeFilter;
2044
0
    }
2045
0
    if (!m_poDS->m_osDateTime.empty())
2046
0
    {
2047
0
        if (osURLNew.find('?') == std::string::npos)
2048
0
            osURLNew += "?";
2049
0
        else
2050
0
            osURLNew += "&";
2051
0
        osURLNew += "datetime=";
2052
0
        osURLNew += m_poDS->m_osDateTime;
2053
0
    }
2054
0
    return osURLNew;
2055
0
}
2056
2057
/************************************************************************/
2058
/*                         GetNextRawFeature()                          */
2059
/************************************************************************/
2060
2061
OGRFeature *OGROAPIFLayer::GetNextRawFeature()
2062
0
{
2063
0
    if (!m_bFeatureDefnEstablished)
2064
0
        EstablishFeatureDefn();
2065
2066
0
    OGRFeature *poSrcFeature = nullptr;
2067
0
    while (true)
2068
0
    {
2069
0
        if (m_poUnderlyingLayer == nullptr)
2070
0
        {
2071
0
            if (m_osGetURL.empty())
2072
0
                return nullptr;
2073
2074
0
            m_oCurDoc = CPLJSONDocument();
2075
2076
0
            const CPLString osURL(m_osGetURL);
2077
0
            m_osGetURL.clear();
2078
0
            CPLStringList aosHeaders;
2079
0
            if (!m_poDS->DownloadJSon(osURL, m_oCurDoc,
2080
0
                                      MEDIA_TYPE_GEOJSON ", " MEDIA_TYPE_JSON,
2081
0
                                      &aosHeaders))
2082
0
            {
2083
0
                return nullptr;
2084
0
            }
2085
2086
0
            const std::string osContentCRS =
2087
0
                aosHeaders.FetchNameValueDef("Content-Crs", "");
2088
0
            if (!m_bHasEmittedContentCRSWarning && !osContentCRS.empty())
2089
0
            {
2090
0
                if (m_osActiveCRS.empty())
2091
0
                {
2092
0
                    if (osContentCRS !=
2093
0
                            "<http://www.opengis.net/def/crs/OGC/1.3/CRS84>" &&
2094
0
                        osContentCRS !=
2095
0
                            "<http://www.opengis.net/def/crs/OGC/0/CRS84h>")
2096
0
                    {
2097
0
                        m_bHasEmittedContentCRSWarning = true;
2098
0
                        CPLDebug("OAPIF",
2099
0
                                 "Got Content-CRS = %s, but expected OGC:CRS84 "
2100
0
                                 "instead. "
2101
0
                                 "Content-CRS will be ignored",
2102
0
                                 osContentCRS.c_str());
2103
0
                    }
2104
0
                }
2105
0
                else
2106
0
                {
2107
0
                    if (osContentCRS != '<' + m_osActiveCRS + '>')
2108
0
                    {
2109
0
                        m_bHasEmittedContentCRSWarning = true;
2110
0
                        CPLDebug(
2111
0
                            "OAPIF",
2112
0
                            "Got Content-CRS = %s, but expected %s instead. "
2113
0
                            "Content-CRS will be ignored",
2114
0
                            osContentCRS.c_str(), m_osActiveCRS.c_str());
2115
0
                    }
2116
0
                }
2117
0
            }
2118
0
            else if (!m_bHasEmittedContentCRSWarning)
2119
0
            {
2120
0
                if (!m_osActiveCRS.empty())
2121
0
                {
2122
0
                    m_bHasEmittedContentCRSWarning = true;
2123
0
                    CPLDebug("OAPIF",
2124
0
                             "Dit not get Content-CRS header. "
2125
0
                             "Assuming %s is returned",
2126
0
                             m_osActiveCRS.c_str());
2127
0
                }
2128
0
            }
2129
2130
0
            if (!m_bHasEmittedJsonCRWarning)
2131
0
            {
2132
0
                const auto oJsonCRS = m_oCurDoc.GetRoot().GetObj("crs");
2133
0
                if (oJsonCRS.IsValid())
2134
0
                {
2135
0
                    m_bHasEmittedJsonCRWarning = true;
2136
0
                    CPLDebug("OAPIF",
2137
0
                             "JSON response contains %s. It will be ignored.",
2138
0
                             oJsonCRS.ToString().c_str());
2139
0
                }
2140
0
            }
2141
2142
0
            const CPLString osTmpFilename(
2143
0
                VSIMemGenerateHiddenFilename("oapif.json"));
2144
0
            m_oCurDoc.Save(osTmpFilename);
2145
0
            m_poUnderlyingDS =
2146
0
                std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
2147
0
                    GDALOpenEx(osTmpFilename, GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
2148
0
                               nullptr, nullptr, nullptr)));
2149
0
            VSIUnlink(osTmpFilename);
2150
0
            if (!m_poUnderlyingDS.get())
2151
0
            {
2152
0
                return nullptr;
2153
0
            }
2154
0
            m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
2155
0
            if (!m_poUnderlyingLayer)
2156
0
            {
2157
0
                m_poUnderlyingDS.reset();
2158
0
                return nullptr;
2159
0
            }
2160
2161
            // To avoid issues with implementations having a non-relevant
2162
            // next link, make sure the current page is not empty
2163
            // We could even check that the feature count is the page size
2164
            // actually
2165
0
            if (m_poUnderlyingLayer->GetFeatureCount() > 0 && m_osGetID.empty())
2166
0
            {
2167
0
                CPLJSONArray oLinks = m_oCurDoc.GetRoot().GetArray("links");
2168
0
                if (oLinks.IsValid())
2169
0
                {
2170
0
                    int nCountRelNext = 0;
2171
0
                    std::string osNextURL;
2172
0
                    for (int i = 0; i < oLinks.Size(); i++)
2173
0
                    {
2174
0
                        CPLJSONObject oLink = oLinks[i];
2175
0
                        if (!oLink.IsValid() ||
2176
0
                            oLink.GetType() != CPLJSONObject::Type::Object)
2177
0
                        {
2178
0
                            continue;
2179
0
                        }
2180
0
                        if (EQUAL(oLink.GetString("rel").c_str(), "next"))
2181
0
                        {
2182
0
                            nCountRelNext++;
2183
0
                            auto type = oLink.GetString("type");
2184
0
                            if (type == MEDIA_TYPE_GEOJSON ||
2185
0
                                type == MEDIA_TYPE_JSON)
2186
0
                            {
2187
0
                                m_osGetURL = oLink.GetString("href");
2188
0
                                break;
2189
0
                            }
2190
0
                            else if (type.empty())
2191
0
                            {
2192
0
                                osNextURL = oLink.GetString("href");
2193
0
                            }
2194
0
                        }
2195
0
                    }
2196
0
                    if (nCountRelNext == 1 && m_osGetURL.empty())
2197
0
                    {
2198
                        // In case we go a "rel": "next" without a "type"
2199
0
                        m_osGetURL = std::move(osNextURL);
2200
0
                    }
2201
0
                }
2202
2203
#ifdef no_longer_used
2204
                // Recommendation /rec/core/link-header
2205
                if (m_osGetURL.empty())
2206
                {
2207
                    for (int i = 0; i < aosHeaders.size(); i++)
2208
                    {
2209
                        CPLDebug("OAPIF", "%s", aosHeaders[i]);
2210
                        if (STARTS_WITH_CI(aosHeaders[i], "Link=") &&
2211
                            strstr(aosHeaders[i], "rel=\"next\"") &&
2212
                            strstr(aosHeaders[i],
2213
                                   "type=\"" MEDIA_TYPE_GEOJSON "\""))
2214
                        {
2215
                            const char *pszStart = strchr(aosHeaders[i], '<');
2216
                            if (pszStart)
2217
                            {
2218
                                const char *pszEnd = strchr(pszStart + 1, '>');
2219
                                if (pszEnd)
2220
                                {
2221
                                    m_osGetURL = pszStart + 1;
2222
                                    m_osGetURL.resize(pszEnd - pszStart - 1);
2223
                                }
2224
                            }
2225
                            break;
2226
                        }
2227
                    }
2228
                }
2229
#endif
2230
2231
0
                if (!m_osGetURL.empty())
2232
0
                {
2233
0
                    m_osGetURL = m_poDS->ResolveURL(m_osGetURL, osURL);
2234
0
                }
2235
0
            }
2236
0
        }
2237
2238
        // cppcheck-suppress nullPointerRedundantCheck
2239
0
        poSrcFeature = m_poUnderlyingLayer->GetNextFeature();
2240
0
        if (poSrcFeature)
2241
0
        {
2242
0
            break;
2243
0
        }
2244
0
        m_poUnderlyingDS.reset();
2245
0
        m_poUnderlyingLayer = nullptr;
2246
0
        m_iFeatureInPage = 0;
2247
0
    }
2248
2249
0
    OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
2250
0
    poFeature->SetFrom(poSrcFeature);
2251
2252
    // Collect STAC assets href
2253
0
    if (!m_aosItemAssetNames.empty() && m_poUnderlyingLayer != nullptr &&
2254
0
        m_oCurDoc.GetRoot().GetArray("features").Size() ==
2255
0
            m_poUnderlyingLayer->GetFeatureCount() &&
2256
0
        m_iFeatureInPage < m_oCurDoc.GetRoot().GetArray("features").Size())
2257
0
    {
2258
0
        auto m_oFeature =
2259
0
            m_oCurDoc.GetRoot().GetArray("features")[m_iFeatureInPage];
2260
0
        auto oAssets = m_oFeature["assets"];
2261
0
        for (const auto &osAssetName : m_aosItemAssetNames)
2262
0
        {
2263
0
            auto href = oAssets[osAssetName]["href"];
2264
0
            if (href.IsValid() && href.GetType() == CPLJSONObject::Type::String)
2265
0
            {
2266
0
                poFeature->SetField(("asset_" + osAssetName + "_href").c_str(),
2267
0
                                    href.ToString().c_str());
2268
0
            }
2269
0
        }
2270
0
    }
2271
0
    m_iFeatureInPage++;
2272
2273
0
    auto poGeom = poFeature->GetGeometryRef();
2274
0
    if (poGeom)
2275
0
    {
2276
0
        if (!m_bCRSHasGISFriendlyOrder &&
2277
0
            !m_poDS->m_bServerFeaturesAxisOrderGISFriendly)
2278
0
            poGeom->swapXY();
2279
0
        poGeom->assignSpatialReference(GetSpatialRef());
2280
0
    }
2281
0
    if (m_bHasIntIdMember)
2282
0
    {
2283
0
        poFeature->SetFID(poSrcFeature->GetFID());
2284
0
    }
2285
0
    else
2286
0
    {
2287
0
        poFeature->SetFID(m_nFID);
2288
0
        m_nFID++;
2289
0
    }
2290
0
    delete poSrcFeature;
2291
0
    return poFeature;
2292
0
}
2293
2294
/************************************************************************/
2295
/*                             GetFeature()                             */
2296
/************************************************************************/
2297
2298
OGRFeature *OGROAPIFLayer::GetFeature(GIntBig nFID)
2299
0
{
2300
0
    if (!m_bFeatureDefnEstablished)
2301
0
        EstablishFeatureDefn();
2302
0
    if (!m_bHasIntIdMember)
2303
0
        return OGRLayer::GetFeature(nFID);
2304
2305
0
    m_osGetID.Printf(CPL_FRMT_GIB, nFID);
2306
0
    ResetReading();
2307
0
    auto poRet = GetNextRawFeature();
2308
0
    m_osGetID.clear();
2309
0
    ResetReading();
2310
0
    return poRet;
2311
0
}
2312
2313
/************************************************************************/
2314
/*                           GetNextFeature()                           */
2315
/************************************************************************/
2316
2317
OGRFeature *OGROAPIFLayer::GetNextFeature()
2318
0
{
2319
0
    while (true)
2320
0
    {
2321
0
        OGRFeature *poFeature = GetNextRawFeature();
2322
0
        if (poFeature == nullptr)
2323
0
            return nullptr;
2324
2325
0
        if ((m_poFilterGeom == nullptr ||
2326
0
             FilterGeometry(poFeature->GetGeometryRef())) &&
2327
0
            (m_poAttrQuery == nullptr || !m_bFilterMustBeClientSideEvaluated ||
2328
0
             m_poAttrQuery->Evaluate(poFeature)))
2329
0
        {
2330
0
            return poFeature;
2331
0
        }
2332
0
        else
2333
0
        {
2334
0
            delete poFeature;
2335
0
        }
2336
0
    }
2337
0
}
2338
2339
/************************************************************************/
2340
/*                       SupportsResultTypeHits()                       */
2341
/************************************************************************/
2342
2343
bool OGROAPIFLayer::SupportsResultTypeHits()
2344
0
{
2345
0
    std::string osAPIURL;
2346
0
    CPLJSONDocument oDoc = m_poDS->GetAPIDoc(osAPIURL);
2347
0
    if (oDoc.GetRoot().GetString("openapi").empty())
2348
0
        return false;
2349
2350
0
    CPLJSONArray oParameters =
2351
0
        oDoc.GetRoot().GetObj("paths").GetObj(m_osPath).GetObj("get").GetArray(
2352
0
            "parameters");
2353
0
    if (!oParameters.IsValid())
2354
0
        return false;
2355
0
    for (int i = 0; i < oParameters.Size(); i++)
2356
0
    {
2357
0
        CPLJSONObject oParam = oParameters[i];
2358
0
        CPLString osRef = oParam.GetString("$ref");
2359
0
        if (!osRef.empty() && osRef.find("#/") == 0)
2360
0
        {
2361
0
            oParam = oDoc.GetRoot().GetObj(osRef.substr(2));
2362
0
#ifndef REMOVE_HACK
2363
            // Needed for
2364
            // http://www.pvretano.com/cubewerx/cubeserv/default/wfs/3.0.0/foundation/api
2365
            // that doesn't define #/components/parameters/resultType
2366
0
            if (osRef == "#/components/parameters/resultType")
2367
0
                return true;
2368
0
#endif
2369
0
        }
2370
0
        if (oParam.GetString("name") == "resultType" &&
2371
0
            oParam.GetString("in") == "query")
2372
0
        {
2373
0
            CPLJSONArray oEnum = oParam.GetArray("schema/enum");
2374
0
            for (int j = 0; j < oEnum.Size(); j++)
2375
0
            {
2376
0
                if (oEnum[j].ToString() == "hits")
2377
0
                    return true;
2378
0
            }
2379
0
            return false;
2380
0
        }
2381
0
    }
2382
2383
0
    return false;
2384
0
}
2385
2386
/************************************************************************/
2387
/*                          GetFeatureCount()                           */
2388
/************************************************************************/
2389
2390
GIntBig OGROAPIFLayer::GetFeatureCount(int bForce)
2391
0
{
2392
2393
0
    if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
2394
0
        m_poDS->m_osDateTime.empty())
2395
0
    {
2396
0
        if (m_nTotalFeatureCount >= 0)
2397
0
        {
2398
0
            return m_nTotalFeatureCount;
2399
0
        }
2400
0
        GetLayerDefn();
2401
0
        if (m_nTotalFeatureCount >= 0)
2402
0
        {
2403
0
            return m_nTotalFeatureCount;
2404
0
        }
2405
0
    }
2406
2407
0
    if (SupportsResultTypeHits() && !m_bFilterMustBeClientSideEvaluated)
2408
0
    {
2409
0
        CPLString osURL(m_osURL);
2410
0
        osURL = CPLURLAddKVP(osURL, "resultType", "hits");
2411
0
        osURL = AddFilters(osURL);
2412
0
#ifndef REMOVE_HACK
2413
0
        bool bGMLRequest = m_osURL.find("cubeserv") != std::string::npos;
2414
#else
2415
        constexpr bool bGMLRequest = false;
2416
#endif
2417
0
        if (bGMLRequest)
2418
0
        {
2419
0
            CPLString osResult;
2420
0
            CPLString osContentType;
2421
0
            if (m_poDS->Download(osURL, MEDIA_TYPE_TEXT_XML, osResult,
2422
0
                                 osContentType))
2423
0
            {
2424
0
                CPLXMLNode *psDoc = CPLParseXMLString(osResult);
2425
0
                if (psDoc)
2426
0
                {
2427
0
                    CPLXMLTreeCloser oCloser(psDoc);
2428
0
                    CPL_IGNORE_RET_VAL(oCloser);
2429
0
                    CPLStripXMLNamespace(psDoc, nullptr, true);
2430
0
                    CPLString osNumberMatched = CPLGetXMLValue(
2431
0
                        psDoc, "=FeatureCollection.numberMatched", "");
2432
0
                    if (!osNumberMatched.empty())
2433
0
                        return CPLAtoGIntBig(osNumberMatched);
2434
0
                }
2435
0
            }
2436
0
        }
2437
0
        else
2438
0
        {
2439
0
            CPLJSONDocument oDoc;
2440
0
            if (m_poDS->DownloadJSon(osURL, oDoc))
2441
0
            {
2442
0
                GIntBig nFeatures = oDoc.GetRoot().GetLong("numberMatched", -1);
2443
0
                if (nFeatures >= 0)
2444
0
                    return nFeatures;
2445
0
            }
2446
0
        }
2447
0
    }
2448
2449
0
    return OGRLayer::GetFeatureCount(bForce);
2450
0
}
2451
2452
/************************************************************************/
2453
/*                             IGetExtent()                             */
2454
/************************************************************************/
2455
2456
OGRErr OGROAPIFLayer::IGetExtent(int iGeomField, OGREnvelope *psEnvelope,
2457
                                 bool bForce)
2458
0
{
2459
0
    if (m_oOriginalExtent.IsInit())
2460
0
    {
2461
0
        if (!m_oExtent.IsInit())
2462
0
            ComputeExtent();
2463
0
        *psEnvelope = m_oExtent;
2464
0
        return OGRERR_NONE;
2465
0
    }
2466
0
    return OGRLayer::IGetExtent(iGeomField, psEnvelope, bForce);
2467
0
}
2468
2469
/************************************************************************/
2470
/*                         ISetSpatialFilter()                          */
2471
/************************************************************************/
2472
2473
OGRErr OGROAPIFLayer::ISetSpatialFilter(int, const OGRGeometry *poGeomIn)
2474
0
{
2475
0
    InstallFilter(poGeomIn);
2476
2477
0
    ResetReading();
2478
0
    return OGRERR_NONE;
2479
0
}
2480
2481
/************************************************************************/
2482
/*                        OGRWF3ParseDateTime()                         */
2483
/************************************************************************/
2484
2485
static int OGRWF3ParseDateTime(const char *pszValue, int &nYear, int &nMonth,
2486
                               int &nDay, int &nHour, int &nMinute,
2487
                               int &nSecond)
2488
0
{
2489
0
    int ret = sscanf(pszValue, "%04d/%02d/%02d %02d:%02d:%02d", &nYear, &nMonth,
2490
0
                     &nDay, &nHour, &nMinute, &nSecond);
2491
0
    if (ret >= 3)
2492
0
        return ret;
2493
0
    return sscanf(pszValue, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth,
2494
0
                  &nDay, &nHour, &nMinute, &nSecond);
2495
0
}
2496
2497
/************************************************************************/
2498
/*                         SerializeDateTime()                          */
2499
/************************************************************************/
2500
2501
static CPLString SerializeDateTime(int nDateComponents, int nYear, int nMonth,
2502
                                   int nDay, int nHour, int nMinute,
2503
                                   int nSecond)
2504
0
{
2505
0
    CPLString osRet;
2506
0
    osRet.Printf("%04d-%02d-%02dT", nYear, nMonth, nDay);
2507
0
    if (nDateComponents >= 4)
2508
0
    {
2509
0
        osRet += CPLSPrintf("%02d", nHour);
2510
0
        if (nDateComponents >= 5)
2511
0
            osRet += CPLSPrintf(":%02d", nMinute);
2512
0
        if (nDateComponents >= 6)
2513
0
            osRet += CPLSPrintf(":%02d", nSecond);
2514
0
        osRet += "Z";
2515
0
    }
2516
0
    return osRet;
2517
0
}
2518
2519
/************************************************************************/
2520
/*                            BuildFilter()                             */
2521
/************************************************************************/
2522
2523
CPLString OGROAPIFLayer::BuildFilter(const swq_expr_node *poNode)
2524
0
{
2525
0
    if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
2526
0
        poNode->nSubExprCount == 2)
2527
0
    {
2528
0
        const auto leftExpr = poNode->papoSubExpr[0];
2529
0
        const auto rightExpr = poNode->papoSubExpr[1];
2530
2531
        // Detect expression: datetime >=|> XXX and datetime <=|< XXXX
2532
0
        if (leftExpr->eNodeType == SNT_OPERATION &&
2533
0
            (leftExpr->nOperation == SWQ_GT ||
2534
0
             leftExpr->nOperation == SWQ_GE) &&
2535
0
            leftExpr->nSubExprCount == 2 &&
2536
0
            leftExpr->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2537
0
            leftExpr->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
2538
0
            rightExpr->eNodeType == SNT_OPERATION &&
2539
0
            (rightExpr->nOperation == SWQ_LT ||
2540
0
             rightExpr->nOperation == SWQ_LE) &&
2541
0
            rightExpr->nSubExprCount == 2 &&
2542
0
            rightExpr->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2543
0
            rightExpr->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
2544
0
            leftExpr->papoSubExpr[0]->field_index ==
2545
0
                rightExpr->papoSubExpr[0]->field_index &&
2546
0
            leftExpr->papoSubExpr[1]->field_type == SWQ_TIMESTAMP &&
2547
0
            rightExpr->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
2548
0
        {
2549
0
            const OGRFieldDefn *poFieldDefn = GetLayerDefn()->GetFieldDefn(
2550
0
                leftExpr->papoSubExpr[0]->field_index);
2551
0
            if (poFieldDefn && (poFieldDefn->GetType() == OFTDate ||
2552
0
                                poFieldDefn->GetType() == OFTDateTime))
2553
0
            {
2554
0
                CPLString osExpr;
2555
0
                {
2556
0
                    int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
2557
0
                        nSecond = 0;
2558
0
                    int nDateComponents = OGRWF3ParseDateTime(
2559
0
                        leftExpr->papoSubExpr[1]->string_value, nYear, nMonth,
2560
0
                        nDay, nHour, nMinute, nSecond);
2561
0
                    if (nDateComponents >= 3)
2562
0
                    {
2563
0
                        osExpr =
2564
0
                            "datetime=" +
2565
0
                            SerializeDateTime(nDateComponents, nYear, nMonth,
2566
0
                                              nDay, nHour, nMinute, nSecond);
2567
0
                    }
2568
0
                }
2569
0
                if (!osExpr.empty())
2570
0
                {
2571
0
                    int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
2572
0
                        nSecond = 0;
2573
0
                    int nDateComponents = OGRWF3ParseDateTime(
2574
0
                        rightExpr->papoSubExpr[1]->string_value, nYear, nMonth,
2575
0
                        nDay, nHour, nMinute, nSecond);
2576
0
                    if (nDateComponents >= 3)
2577
0
                    {
2578
0
                        osExpr +=
2579
0
                            "%2F"  // '/' URL encoded
2580
0
                            + SerializeDateTime(nDateComponents, nYear, nMonth,
2581
0
                                                nDay, nHour, nMinute, nSecond);
2582
0
                        return osExpr;
2583
0
                    }
2584
0
                }
2585
0
            }
2586
0
        }
2587
2588
        // For AND, we can deal with a failure in one of the branch
2589
        // since client-side will do that extra filtering
2590
0
        CPLString osFilter1 = BuildFilter(leftExpr);
2591
0
        CPLString osFilter2 = BuildFilter(rightExpr);
2592
0
        if (!osFilter1.empty() && !osFilter2.empty())
2593
0
        {
2594
0
            return osFilter1 + "&" + osFilter2;
2595
0
        }
2596
0
        else if (!osFilter1.empty())
2597
0
            return osFilter1;
2598
0
        else
2599
0
            return osFilter2;
2600
0
    }
2601
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2602
0
             poNode->nOperation == SWQ_EQ && poNode->nSubExprCount == 2 &&
2603
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2604
0
             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT)
2605
0
    {
2606
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
2607
0
        const OGRFieldDefn *poFieldDefn =
2608
0
            GetLayerDefn()->GetFieldDefn(nFieldIdx);
2609
0
        int nDateComponents;
2610
0
        int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
2611
0
            nSecond = 0;
2612
0
        if (m_bHasStringIdMember &&
2613
0
            strcmp(poFieldDefn->GetNameRef(), "id") == 0 &&
2614
0
            poNode->papoSubExpr[1]->field_type == SWQ_STRING)
2615
0
        {
2616
0
            m_osGetID = poNode->papoSubExpr[1]->string_value;
2617
0
        }
2618
0
        else if (poFieldDefn &&
2619
0
                 m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
2620
0
                     m_aoSetQueryableAttributes.end())
2621
0
        {
2622
0
            char *pszEscapedFieldName =
2623
0
                CPLEscapeString(poFieldDefn->GetNameRef(), -1, CPLES_URL);
2624
0
            CPLString osEscapedFieldName(pszEscapedFieldName);
2625
0
            CPLFree(pszEscapedFieldName);
2626
2627
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_STRING)
2628
0
            {
2629
0
                char *pszEscapedValue = CPLEscapeString(
2630
0
                    poNode->papoSubExpr[1]->string_value, -1, CPLES_URL);
2631
0
                CPLString osRet(std::move(osEscapedFieldName));
2632
0
                osRet += "=";
2633
0
                osRet += pszEscapedValue;
2634
0
                CPLFree(pszEscapedValue);
2635
0
                return osRet;
2636
0
            }
2637
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_INTEGER)
2638
0
            {
2639
0
                CPLString osRet(std::move(osEscapedFieldName));
2640
0
                osRet += "=";
2641
0
                osRet +=
2642
0
                    CPLSPrintf("%" PRId64, poNode->papoSubExpr[1]->int_value);
2643
0
                return osRet;
2644
0
            }
2645
0
        }
2646
0
        else if (poFieldDefn &&
2647
0
                 (poFieldDefn->GetType() == OFTDate ||
2648
0
                  poFieldDefn->GetType() == OFTDateTime) &&
2649
0
                 poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP &&
2650
0
                 (nDateComponents = OGRWF3ParseDateTime(
2651
0
                      poNode->papoSubExpr[1]->string_value, nYear, nMonth, nDay,
2652
0
                      nHour, nMinute, nSecond)) >= 3)
2653
0
        {
2654
0
            return "datetime=" + SerializeDateTime(nDateComponents, nYear,
2655
0
                                                   nMonth, nDay, nHour, nMinute,
2656
0
                                                   nSecond);
2657
0
        }
2658
0
    }
2659
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2660
0
             (poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
2661
0
              poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE) &&
2662
0
             poNode->nSubExprCount == 2 &&
2663
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2664
0
             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
2665
0
             poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
2666
0
    {
2667
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
2668
0
        const OGRFieldDefn *poFieldDefn =
2669
0
            GetLayerDefn()->GetFieldDefn(nFieldIdx);
2670
0
        int nDateComponents;
2671
0
        int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
2672
0
            nSecond = 0;
2673
0
        if (poFieldDefn &&
2674
0
            (poFieldDefn->GetType() == OFTDate ||
2675
0
             poFieldDefn->GetType() == OFTDateTime) &&
2676
0
            (nDateComponents = OGRWF3ParseDateTime(
2677
0
                 poNode->papoSubExpr[1]->string_value, nYear, nMonth, nDay,
2678
0
                 nHour, nMinute, nSecond)) >= 3)
2679
0
        {
2680
0
            CPLString osDT(SerializeDateTime(nDateComponents, nYear, nMonth,
2681
0
                                             nDay, nHour, nMinute, nSecond));
2682
0
            if (poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE)
2683
0
            {
2684
0
                return "datetime=" + osDT + "%2F..";
2685
0
            }
2686
0
            else
2687
0
            {
2688
0
                return "datetime=..%2F" + osDT;
2689
0
            }
2690
0
        }
2691
0
    }
2692
0
    m_bFilterMustBeClientSideEvaluated = true;
2693
0
    return CPLString();
2694
0
}
2695
2696
/************************************************************************/
2697
/*                         BuildFilterCQLText()                         */
2698
/************************************************************************/
2699
2700
CPLString OGROAPIFLayer::BuildFilterCQLText(const swq_expr_node *poNode)
2701
0
{
2702
0
    if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
2703
0
        poNode->nSubExprCount == 2)
2704
0
    {
2705
0
        const auto leftExpr = poNode->papoSubExpr[0];
2706
0
        const auto rightExpr = poNode->papoSubExpr[1];
2707
2708
        // For AND, we can deal with a failure in one of the branch
2709
        // since client-side will do that extra filtering
2710
0
        CPLString osFilter1 = BuildFilterCQLText(leftExpr);
2711
0
        CPLString osFilter2 = BuildFilterCQLText(rightExpr);
2712
0
        if (!osFilter1.empty() && !osFilter2.empty())
2713
0
        {
2714
0
            return '(' + osFilter1 + ") AND (" + osFilter2 + ')';
2715
0
        }
2716
0
        else if (!osFilter1.empty())
2717
0
            return osFilter1;
2718
0
        else
2719
0
            return osFilter2;
2720
0
    }
2721
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2722
0
             poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2)
2723
0
    {
2724
0
        const auto leftExpr = poNode->papoSubExpr[0];
2725
0
        const auto rightExpr = poNode->papoSubExpr[1];
2726
2727
0
        CPLString osFilter1 = BuildFilterCQLText(leftExpr);
2728
0
        CPLString osFilter2 = BuildFilterCQLText(rightExpr);
2729
0
        if (!osFilter1.empty() && !osFilter2.empty())
2730
0
        {
2731
0
            return '(' + osFilter1 + ") OR (" + osFilter2 + ')';
2732
0
        }
2733
0
    }
2734
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2735
0
             poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1)
2736
0
    {
2737
0
        const auto childExpr = poNode->papoSubExpr[0];
2738
0
        CPLString osFilterChild = BuildFilterCQLText(childExpr);
2739
0
        if (!osFilterChild.empty())
2740
0
        {
2741
0
            return "NOT (" + osFilterChild + ')';
2742
0
        }
2743
0
    }
2744
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2745
0
             poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1 &&
2746
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN)
2747
0
    {
2748
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
2749
0
        const OGRFieldDefn *poFieldDefn =
2750
0
            GetLayerDefn()->GetFieldDefn(nFieldIdx);
2751
0
        if (poFieldDefn)
2752
0
        {
2753
0
            return CPLString("(") + poFieldDefn->GetNameRef() + " IS NULL)";
2754
0
        }
2755
0
    }
2756
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2757
0
             (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_NE ||
2758
0
              poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
2759
0
              poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE ||
2760
0
              poNode->nOperation == SWQ_LIKE ||
2761
0
              poNode->nOperation == SWQ_ILIKE) &&
2762
0
             poNode->nSubExprCount == 2 &&
2763
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2764
0
             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT)
2765
0
    {
2766
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
2767
0
        const OGRFieldDefn *poFieldDefn =
2768
0
            GetLayerDefn()->GetFieldDefn(nFieldIdx);
2769
0
        if (m_bHasStringIdMember && poNode->nOperation == SWQ_EQ &&
2770
0
            strcmp(poFieldDefn->GetNameRef(), "id") == 0 &&
2771
0
            poNode->papoSubExpr[1]->field_type == SWQ_STRING)
2772
0
        {
2773
0
            m_osGetID = poNode->papoSubExpr[1]->string_value;
2774
0
        }
2775
0
        else if (poFieldDefn &&
2776
0
                 m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
2777
0
                     m_aoSetQueryableAttributes.end())
2778
0
        {
2779
0
            CPLString osRet(poFieldDefn->GetNameRef());
2780
0
            switch (poNode->nOperation)
2781
0
            {
2782
0
                case SWQ_EQ:
2783
0
                    osRet += " = ";
2784
0
                    break;
2785
0
                case SWQ_NE:
2786
0
                    osRet += " <> ";
2787
0
                    break;
2788
0
                case SWQ_GT:
2789
0
                    osRet += " > ";
2790
0
                    break;
2791
0
                case SWQ_GE:
2792
0
                    osRet += " >= ";
2793
0
                    break;
2794
0
                case SWQ_LT:
2795
0
                    osRet += " < ";
2796
0
                    break;
2797
0
                case SWQ_LE:
2798
0
                    osRet += " <= ";
2799
0
                    break;
2800
0
                case SWQ_LIKE:
2801
0
                    osRet += " LIKE ";
2802
0
                    break;
2803
0
                case SWQ_ILIKE:
2804
0
                    osRet += " ILIKE ";
2805
0
                    break;
2806
0
                default:
2807
0
                    CPLAssert(false);
2808
0
                    break;
2809
0
            }
2810
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_STRING)
2811
0
            {
2812
0
                osRet += '\'';
2813
0
                osRet += CPLString(poNode->papoSubExpr[1]->string_value)
2814
0
                             .replaceAll('\'', "''");
2815
0
                osRet += '\'';
2816
0
                return osRet;
2817
0
            }
2818
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_INTEGER ||
2819
0
                poNode->papoSubExpr[1]->field_type == SWQ_INTEGER64)
2820
0
            {
2821
0
                osRet +=
2822
0
                    CPLSPrintf("%" PRId64, poNode->papoSubExpr[1]->int_value);
2823
0
                return osRet;
2824
0
            }
2825
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_FLOAT)
2826
0
            {
2827
0
                osRet +=
2828
0
                    CPLSPrintf("%.16g", poNode->papoSubExpr[1]->float_value);
2829
0
                return osRet;
2830
0
            }
2831
0
            if (poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
2832
0
            {
2833
0
                int nDateComponents;
2834
0
                int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
2835
0
                    nSecond = 0;
2836
0
                if ((poFieldDefn->GetType() == OFTDate ||
2837
0
                     poFieldDefn->GetType() == OFTDateTime) &&
2838
0
                    (nDateComponents = OGRWF3ParseDateTime(
2839
0
                         poNode->papoSubExpr[1]->string_value, nYear, nMonth,
2840
0
                         nDay, nHour, nMinute, nSecond)) >= 3)
2841
0
                {
2842
0
                    CPLString osDT(SerializeDateTime(nDateComponents, nYear,
2843
0
                                                     nMonth, nDay, nHour,
2844
0
                                                     nMinute, nSecond));
2845
0
                    osRet += '\'';
2846
0
                    osRet += osDT;
2847
0
                    osRet += '\'';
2848
0
                    return osRet;
2849
0
                }
2850
0
            }
2851
0
        }
2852
0
    }
2853
2854
0
    m_bFilterMustBeClientSideEvaluated = true;
2855
0
    return CPLString();
2856
0
}
2857
2858
/************************************************************************/
2859
/*                     BuildFilterJSONFilterExpr()                      */
2860
/************************************************************************/
2861
2862
CPLString OGROAPIFLayer::BuildFilterJSONFilterExpr(const swq_expr_node *poNode)
2863
0
{
2864
0
    if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
2865
0
        poNode->nSubExprCount == 2)
2866
0
    {
2867
0
        const auto leftExpr = poNode->papoSubExpr[0];
2868
0
        const auto rightExpr = poNode->papoSubExpr[1];
2869
2870
        // For AND, we can deal with a failure in one of the branch
2871
        // since client-side will do that extra filtering
2872
0
        CPLString osFilter1 = BuildFilterJSONFilterExpr(leftExpr);
2873
0
        CPLString osFilter2 = BuildFilterJSONFilterExpr(rightExpr);
2874
0
        if (!osFilter1.empty() && !osFilter2.empty())
2875
0
        {
2876
0
            return "[\"all\"," + osFilter1 + ',' + osFilter2 + ']';
2877
0
        }
2878
0
        else if (!osFilter1.empty())
2879
0
            return osFilter1;
2880
0
        else
2881
0
            return osFilter2;
2882
0
    }
2883
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2884
0
             poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2)
2885
0
    {
2886
0
        const auto leftExpr = poNode->papoSubExpr[0];
2887
0
        const auto rightExpr = poNode->papoSubExpr[1];
2888
2889
0
        CPLString osFilter1 = BuildFilterJSONFilterExpr(leftExpr);
2890
0
        CPLString osFilter2 = BuildFilterJSONFilterExpr(rightExpr);
2891
0
        if (!osFilter1.empty() && !osFilter2.empty())
2892
0
        {
2893
0
            return "[\"any\"," + osFilter1 + ',' + osFilter2 + ']';
2894
0
        }
2895
0
    }
2896
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2897
0
             poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1)
2898
0
    {
2899
0
        const auto childExpr = poNode->papoSubExpr[0];
2900
0
        CPLString osFilterChild = BuildFilterJSONFilterExpr(childExpr);
2901
0
        if (!osFilterChild.empty())
2902
0
        {
2903
0
            return "[\"!\"," + osFilterChild + ']';
2904
0
        }
2905
0
    }
2906
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2907
0
             poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1)
2908
0
    {
2909
0
        const auto childExpr = poNode->papoSubExpr[0];
2910
0
        CPLString osFilterChild = BuildFilterJSONFilterExpr(childExpr);
2911
0
        if (!osFilterChild.empty())
2912
0
        {
2913
0
            return "[\"==\"," + osFilterChild + ",null]";
2914
0
        }
2915
0
    }
2916
0
    else if (poNode->eNodeType == SNT_OPERATION &&
2917
0
             (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_NE ||
2918
0
              poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE ||
2919
0
              poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE ||
2920
0
              poNode->nOperation == SWQ_LIKE) &&
2921
0
             poNode->nSubExprCount == 2)
2922
0
    {
2923
0
        if (m_bHasStringIdMember && poNode->nOperation == SWQ_EQ &&
2924
0
            poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
2925
0
            poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
2926
0
            poNode->papoSubExpr[1]->field_type == SWQ_STRING)
2927
0
        {
2928
0
            const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
2929
0
            const OGRFieldDefn *poFieldDefn =
2930
0
                GetLayerDefn()->GetFieldDefn(nFieldIdx);
2931
0
            if (strcmp(poFieldDefn->GetNameRef(), "id") == 0)
2932
0
            {
2933
0
                m_osGetID = poNode->papoSubExpr[1]->string_value;
2934
0
                return CPLString();
2935
0
            }
2936
0
        }
2937
2938
0
        CPLString osRet("[\"");
2939
0
        switch (poNode->nOperation)
2940
0
        {
2941
0
            case SWQ_EQ:
2942
0
                osRet += "==";
2943
0
                break;
2944
0
            case SWQ_NE:
2945
0
                osRet += "!=";
2946
0
                break;
2947
0
            case SWQ_GT:
2948
0
                osRet += ">";
2949
0
                break;
2950
0
            case SWQ_GE:
2951
0
                osRet += ">=";
2952
0
                break;
2953
0
            case SWQ_LT:
2954
0
                osRet += "<";
2955
0
                break;
2956
0
            case SWQ_LE:
2957
0
                osRet += "<=";
2958
0
                break;
2959
0
            case SWQ_LIKE:
2960
0
                osRet += "like";
2961
0
                break;
2962
0
            default:
2963
0
                CPLAssert(false);
2964
0
                break;
2965
0
        }
2966
0
        osRet += "\",";
2967
0
        CPLString osFilter1 = BuildFilterJSONFilterExpr(poNode->papoSubExpr[0]);
2968
0
        CPLString osFilter2 = BuildFilterJSONFilterExpr(poNode->papoSubExpr[1]);
2969
0
        if (!osFilter1.empty() && !osFilter2.empty())
2970
0
        {
2971
0
            osRet += osFilter1;
2972
0
            osRet += ',';
2973
0
            osRet += osFilter2;
2974
0
            osRet += ']';
2975
0
            return osRet;
2976
0
        }
2977
0
    }
2978
0
    else if (poNode->eNodeType == SNT_COLUMN)
2979
0
    {
2980
0
        const int nFieldIdx = poNode->field_index;
2981
0
        const OGRFieldDefn *poFieldDefn =
2982
0
            GetLayerDefn()->GetFieldDefn(nFieldIdx);
2983
0
        if (poFieldDefn &&
2984
0
            m_aoSetQueryableAttributes.find(poFieldDefn->GetNameRef()) !=
2985
0
                m_aoSetQueryableAttributes.end())
2986
0
        {
2987
0
            CPLString osRet("[\"get\",\"");
2988
0
            osRet += CPLString(poFieldDefn->GetNameRef())
2989
0
                         .replaceAll('\\', "\\\\")
2990
0
                         .replaceAll('"', "\\\"");
2991
0
            osRet += "\"]";
2992
0
            return osRet;
2993
0
        }
2994
0
    }
2995
0
    else if (poNode->eNodeType == SNT_CONSTANT)
2996
0
    {
2997
0
        if (poNode->field_type == SWQ_STRING)
2998
0
        {
2999
0
            CPLString osRet("\"");
3000
0
            osRet += CPLString(poNode->string_value)
3001
0
                         .replaceAll('\\', "\\\\")
3002
0
                         .replaceAll('"', "\\\"");
3003
0
            osRet += '"';
3004
0
            return osRet;
3005
0
        }
3006
0
        if (poNode->field_type == SWQ_INTEGER ||
3007
0
            poNode->field_type == SWQ_INTEGER64)
3008
0
        {
3009
0
            return CPLSPrintf("%" PRId64, poNode->int_value);
3010
0
        }
3011
0
        if (poNode->field_type == SWQ_FLOAT)
3012
0
        {
3013
0
            return CPLSPrintf("%.16g", poNode->float_value);
3014
0
        }
3015
0
        if (poNode->field_type == SWQ_TIMESTAMP)
3016
0
        {
3017
0
            int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMinute = 0,
3018
0
                nSecond = 0;
3019
0
            const int nDateComponents =
3020
0
                OGRWF3ParseDateTime(poNode->string_value, nYear, nMonth, nDay,
3021
0
                                    nHour, nMinute, nSecond);
3022
0
            if (nDateComponents >= 3)
3023
0
            {
3024
0
                CPLString osDT(SerializeDateTime(nDateComponents, nYear, nMonth,
3025
0
                                                 nDay, nHour, nMinute,
3026
0
                                                 nSecond));
3027
0
                CPLString osRet("\"");
3028
0
                osRet += osDT;
3029
0
                osRet += '"';
3030
0
                return osRet;
3031
0
            }
3032
0
        }
3033
0
    }
3034
3035
0
    m_bFilterMustBeClientSideEvaluated = true;
3036
0
    return CPLString();
3037
0
}
3038
3039
/************************************************************************/
3040
/*                       GetQueryableAttributes()                       */
3041
/************************************************************************/
3042
3043
void OGROAPIFLayer::GetQueryableAttributes()
3044
0
{
3045
0
    if (m_bGotQueryableAttributes)
3046
0
        return;
3047
0
    m_bGotQueryableAttributes = true;
3048
0
    std::string osAPIURL;
3049
0
    CPLJSONDocument oAPIDoc = m_poDS->GetAPIDoc(osAPIURL);
3050
0
    if (oAPIDoc.GetRoot().GetString("openapi").empty())
3051
0
        return;
3052
3053
0
    CPLJSONObject oPaths = oAPIDoc.GetRoot().GetObj("paths");
3054
0
    CPLJSONArray oParameters =
3055
0
        oPaths.GetObj(m_osPath).GetObj("get").GetArray("parameters");
3056
0
    if (!oParameters.IsValid())
3057
0
    {
3058
0
        oParameters = oPaths.GetObj("/collections/{collectionId}/items")
3059
0
                          .GetObj("get")
3060
0
                          .GetArray("parameters");
3061
0
    }
3062
0
    for (int i = 0; i < oParameters.Size(); i++)
3063
0
    {
3064
0
        CPLJSONObject oParam = oParameters[i];
3065
0
        CPLString osRef = oParam.GetString("$ref");
3066
0
        if (!osRef.empty() && osRef.find("#/") == 0)
3067
0
        {
3068
0
            oParam = oAPIDoc.GetRoot().GetObj(osRef.substr(2));
3069
0
        }
3070
0
        if (oParam.GetString("in") == "query")
3071
0
        {
3072
0
            const auto osName(oParam.GetString("name"));
3073
0
            if (osName == "filter-lang")
3074
0
            {
3075
0
                const auto oEnums = oParam.GetObj("schema").GetArray("enum");
3076
0
                for (int j = 0; j < oEnums.Size(); j++)
3077
0
                {
3078
0
                    if (oEnums[j].ToString() == "cql-text")
3079
0
                    {
3080
0
                        m_bHasCQLText = true;
3081
0
                        CPLDebug("OAPIF", "CQL text detected");
3082
0
                    }
3083
0
                    else if (oEnums[j].ToString() == "json-filter-expr")
3084
0
                    {
3085
0
                        m_bHasJSONFilterExpression = true;
3086
0
                        CPLDebug("OAPIF", "JSON Filter expression detected");
3087
0
                    }
3088
0
                }
3089
0
            }
3090
0
            else if (GetLayerDefn()->GetFieldIndex(osName.c_str()) >= 0)
3091
0
            {
3092
0
                m_aoSetQueryableAttributes.insert(osName);
3093
0
            }
3094
0
        }
3095
0
    }
3096
3097
    // HACK
3098
0
    if (CPLTestBool(CPLGetConfigOption("OGR_OAPIF_ALLOW_CQL_TEXT", "NO")))
3099
0
        m_bHasCQLText = true;
3100
3101
0
    if (m_bHasCQLText || m_bHasJSONFilterExpression)
3102
0
    {
3103
0
        if (!m_osQueryablesURL.empty())
3104
0
        {
3105
0
            CPLJSONDocument oDoc;
3106
0
            if (m_poDS->DownloadJSon(m_osQueryablesURL, oDoc))
3107
0
            {
3108
0
                auto oQueryables = oDoc.GetRoot().GetArray("queryables");
3109
0
                if (oQueryables.IsValid())  // deprecated, prior to Part-3
3110
0
                {
3111
0
                    for (int i = 0; i < oQueryables.Size(); i++)
3112
0
                    {
3113
0
                        const auto osId = oQueryables[i].GetString("id");
3114
0
                        if (!osId.empty())
3115
0
                        {
3116
0
                            m_aoSetQueryableAttributes.insert(osId);
3117
0
                        }
3118
0
                    }
3119
0
                }
3120
0
                else
3121
0
                {
3122
0
                    auto oProperties = oDoc.GetRoot().GetObj("properties");
3123
0
                    for (const auto &property : oProperties.GetChildren())
3124
0
                    {
3125
0
                        if (property.GetString("x-ogc-role") !=
3126
0
                            "primary-geometry")
3127
0
                            m_aoSetQueryableAttributes.insert(
3128
0
                                property.GetName());
3129
0
                    }
3130
0
                }
3131
0
            }
3132
0
        }
3133
0
    }
3134
0
}
3135
3136
/************************************************************************/
3137
/*                         SetAttributeFilter()                         */
3138
/************************************************************************/
3139
3140
OGRErr OGROAPIFLayer::SetAttributeFilter(const char *pszQuery)
3141
3142
0
{
3143
0
    if (m_poAttrQuery == nullptr && pszQuery == nullptr)
3144
0
        return OGRERR_NONE;
3145
3146
0
    if (!m_bFeatureDefnEstablished)
3147
0
        EstablishFeatureDefn();
3148
3149
0
    OGRErr eErr = OGRLayer::SetAttributeFilter(pszQuery);
3150
3151
0
    m_osAttributeFilter.clear();
3152
0
    m_bFilterMustBeClientSideEvaluated = false;
3153
0
    m_osGetID.clear();
3154
0
    if (m_poAttrQuery != nullptr)
3155
0
    {
3156
0
        GetQueryableAttributes();
3157
3158
0
        swq_expr_node *poNode =
3159
0
            static_cast<swq_expr_node *>(m_poAttrQuery->GetSWQExpr());
3160
3161
0
        poNode->ReplaceBetweenByGEAndLERecurse();
3162
3163
0
        if (m_bHasCQLText)
3164
0
        {
3165
0
            m_osAttributeFilter = BuildFilterCQLText(poNode);
3166
0
            if (!m_osAttributeFilter.empty())
3167
0
            {
3168
0
                char *pszEscaped =
3169
0
                    CPLEscapeString(m_osAttributeFilter, -1, CPLES_URL);
3170
0
                m_osAttributeFilter = "filter=";
3171
0
                m_osAttributeFilter += pszEscaped;
3172
0
                m_osAttributeFilter += "&filter-lang=cql-text";
3173
0
                CPLFree(pszEscaped);
3174
0
            }
3175
0
        }
3176
0
        else if (m_bHasJSONFilterExpression)
3177
0
        {
3178
0
            m_osAttributeFilter = BuildFilterJSONFilterExpr(poNode);
3179
0
            if (!m_osAttributeFilter.empty())
3180
0
            {
3181
0
                char *pszEscaped =
3182
0
                    CPLEscapeString(m_osAttributeFilter, -1, CPLES_URL);
3183
0
                m_osAttributeFilter = "filter=";
3184
0
                m_osAttributeFilter += pszEscaped;
3185
0
                m_osAttributeFilter += "&filter-lang=json-filter-expr";
3186
0
                CPLFree(pszEscaped);
3187
0
            }
3188
0
        }
3189
0
        else
3190
0
        {
3191
0
            m_osAttributeFilter = BuildFilter(poNode);
3192
0
        }
3193
0
        if (m_osAttributeFilter.empty())
3194
0
        {
3195
0
            CPLDebug("OAPIF", "Full filter will be evaluated on client side.");
3196
0
        }
3197
0
        else if (m_bFilterMustBeClientSideEvaluated)
3198
0
        {
3199
0
            CPLDebug(
3200
0
                "OAPIF",
3201
0
                "Only part of the filter will be evaluated on server side.");
3202
0
        }
3203
0
    }
3204
3205
0
    ResetReading();
3206
3207
0
    return eErr;
3208
0
}
3209
3210
/************************************************************************/
3211
/*                           TestCapability()                           */
3212
/************************************************************************/
3213
3214
int OGROAPIFLayer::TestCapability(const char *pszCap) const
3215
0
{
3216
0
    if (EQUAL(pszCap, OLCFastFeatureCount))
3217
0
    {
3218
0
        return m_nTotalFeatureCount >= 0 && m_poFilterGeom == nullptr &&
3219
0
               m_poAttrQuery == nullptr;
3220
0
    }
3221
0
    if (EQUAL(pszCap, OLCFastGetExtent))
3222
0
    {
3223
0
        return m_oOriginalExtent.IsInit();
3224
0
    }
3225
0
    if (EQUAL(pszCap, OLCStringsAsUTF8))
3226
0
    {
3227
0
        return TRUE;
3228
0
    }
3229
    // Don't advertise OLCRandomRead as it requires a GET per feature
3230
0
    return FALSE;
3231
0
}
3232
3233
/************************************************************************/
3234
/*                                Open()                                */
3235
/************************************************************************/
3236
3237
static GDALDataset *OGROAPIFDriverOpen(GDALOpenInfo *poOpenInfo)
3238
3239
363
{
3240
363
    if (!OGROAPIFDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
3241
0
        return nullptr;
3242
363
    auto poDataset = std::make_unique<OGROAPIFDataset>();
3243
363
    if (!poDataset->Open(poOpenInfo))
3244
363
        return nullptr;
3245
0
    return poDataset.release();
3246
363
}
3247
3248
/************************************************************************/
3249
/*                          RegisterOGROAPIF()                          */
3250
/************************************************************************/
3251
3252
void RegisterOGROAPIF()
3253
3254
22
{
3255
22
    if (GDALGetDriverByName("OAPIF") != nullptr)
3256
0
        return;
3257
3258
22
    GDALDriver *poDriver = new GDALDriver();
3259
3260
22
    poDriver->SetDescription("OAPIF");
3261
22
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
3262
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGC API - Features");
3263
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/oapif.html");
3264
3265
22
    poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "OAPIF:");
3266
22
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
3267
3268
22
    poDriver->SetMetadataItem(
3269
22
        GDAL_DMD_OPENOPTIONLIST,
3270
22
        "<OpenOptionList>"
3271
22
        "  <Option name='URL' type='string' "
3272
22
        "description='URL to the landing page or a /collections/{id}' "
3273
22
        "required='true'/>"
3274
22
        "  <Option name='PAGE_SIZE' type='int' "
3275
22
        "description='Maximum number of features to retrieve in a single "
3276
22
        "request'/>"
3277
22
        "  <Option name='INITIAL_REQUEST_PAGE_SIZE' type='int' "
3278
22
        "description='Maximum number of features to retrieve in the initial "
3279
22
        "request issued to determine the schema from a feature sample'/>"
3280
22
        "  <Option name='USERPWD' type='string' "
3281
22
        "description='Basic authentication as username:password'/>"
3282
22
        "  <Option name='IGNORE_SCHEMA' type='boolean' "
3283
22
        "description='Whether the XML Schema or JSON Schema should be ignored' "
3284
22
        "default='NO'/>"
3285
22
        "  <Option name='CRS' type='string' "
3286
22
        "description='CRS identifier to use for layers'/>"
3287
22
        "  <Option name='PREFERRED_CRS' type='string' "
3288
22
        "description='Preferred CRS identifier to use for layers'/>"
3289
22
        "  <Option name='SERVER_FEATURE_AXIS_ORDER' type='string-select' "
3290
22
        "description='Coordinate axis order of GeoJSON features returned by "
3291
22
        "the server' "
3292
22
        "default='AUTHORITY_COMPLIANT'>"
3293
22
        "    <Value>AUTHORITY_COMPLIANT</Value>"
3294
22
        "    <Value>GIS_FRIENDLY</Value>"
3295
22
        "  </Option>"
3296
22
        "  <Option name='DATETIME' type='string' "
3297
22
        "description=\"Date-time filter to pass to items requests with the "
3298
22
        "'datetime' parameter\"/>"
3299
22
        "</OpenOptionList>");
3300
3301
22
    poDriver->pfnIdentify = OGROAPIFDriverIdentify;
3302
22
    poDriver->pfnOpen = OGROAPIFDriverOpen;
3303
3304
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
3305
22
}