Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/eeda/eedadataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Earth Engine Data API driver
4
 * Purpose:  Earth Engine Data API driver
5
 * Author:   Even Rouault, even dot rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2017-2018, Planet Labs
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdal_priv.h"
14
#include "gdal_frmts.h"
15
#include "ogrsf_frmts.h"
16
#include "cpl_http.h"
17
#include "cpl_conv.h"
18
#include "ogrlibjsonutils.h"
19
#include "ogr_swq.h"
20
#include "eeda.h"
21
22
#include <algorithm>
23
#include <cinttypes>
24
#include <vector>
25
#include <map>
26
#include <set>
27
#include <limits>
28
29
/************************************************************************/
30
/*                     CPLEscapeURLQueryParameter()                     */
31
/************************************************************************/
32
33
static CPLString CPLEscapeURLQueryParameter(const char *pszInput)
34
0
{
35
0
    int nLength = static_cast<int>(strlen(pszInput));
36
37
0
    const size_t nSizeAlloc = nLength * 4 + 1;
38
0
    char *pszOutput = static_cast<char *>(CPLMalloc(nSizeAlloc));
39
0
    int iOut = 0;
40
41
0
    for (int iIn = 0; iIn < nLength; ++iIn)
42
0
    {
43
0
        if ((pszInput[iIn] >= 'a' && pszInput[iIn] <= 'z') ||
44
0
            (pszInput[iIn] >= 'A' && pszInput[iIn] <= 'Z') ||
45
0
            (pszInput[iIn] >= '0' && pszInput[iIn] <= '9'))
46
0
        {
47
0
            pszOutput[iOut++] = pszInput[iIn];
48
0
        }
49
0
        else
50
0
        {
51
0
            snprintf(pszOutput + iOut, nSizeAlloc - iOut, "%%%02X",
52
0
                     static_cast<unsigned char>(pszInput[iIn]));
53
0
            iOut += 3;
54
0
        }
55
0
    }
56
0
    pszOutput[iOut] = '\0';
57
58
0
    CPLString osRet(pszOutput);
59
0
    CPLFree(pszOutput);
60
0
    return osRet;
61
0
}
62
63
/************************************************************************/
64
/*                           GDALEEDADataset                            */
65
/************************************************************************/
66
67
class GDALEEDALayer;
68
69
class GDALEEDADataset final : public GDALEEDABaseDataset
70
{
71
    CPL_DISALLOW_COPY_ASSIGN(GDALEEDADataset)
72
73
    GDALEEDALayer *m_poLayer;
74
75
  public:
76
    GDALEEDADataset();
77
    ~GDALEEDADataset() override;
78
79
    int GetLayerCount() const override
80
0
    {
81
0
        return m_poLayer ? 1 : 0;
82
0
    }
83
84
    const OGRLayer *GetLayer(int idx) const override;
85
86
    bool Open(GDALOpenInfo *poOpenInfo);
87
    json_object *RunRequest(const CPLString &osURL);
88
89
    const CPLString &GetBaseURL() const
90
0
    {
91
0
        return m_osBaseURL;
92
0
    }
93
};
94
95
/************************************************************************/
96
/*                            GDALEEDALayer                             */
97
/************************************************************************/
98
99
class GDALEEDALayer final : public OGRLayer
100
{
101
    CPL_DISALLOW_COPY_ASSIGN(GDALEEDALayer)
102
103
    GDALEEDADataset *m_poDS;
104
    CPLString m_osCollection{};
105
    CPLString m_osCollectionName{};
106
    OGRFeatureDefn *m_poFeatureDefn;
107
    json_object *m_poCurPageObj;
108
    json_object *m_poCurPageAssets;
109
    int m_nIndexInPage;
110
    GIntBig m_nFID;
111
    CPLString m_osAttributeFilter{};
112
    CPLString m_osStartTime{};
113
    CPLString m_osEndTime{};
114
    bool m_bFilterMustBeClientSideEvaluated;
115
    std::set<int> m_oSetQueryableFields{};
116
    std::map<CPLString, CPLString> m_oMapCodeToWKT{};
117
118
    OGRFeature *GetNextRawFeature();
119
    bool IsSimpleComparison(const swq_expr_node *poNode);
120
    CPLString BuildFilter(swq_expr_node *poNode, bool bIsAndTopLevel);
121
122
  public:
123
    GDALEEDALayer(GDALEEDADataset *poDS, const CPLString &osCollection,
124
                  const CPLString &osCollectionName, json_object *poAsset,
125
                  json_object *poLayerConf);
126
    ~GDALEEDALayer() override;
127
128
    void ResetReading() override;
129
    OGRFeature *GetNextFeature() override;
130
    int TestCapability(const char *) const override;
131
132
    const OGRFeatureDefn *GetLayerDefn() const override
133
0
    {
134
0
        return m_poFeatureDefn;
135
0
    }
136
137
    GIntBig GetFeatureCount(int) override
138
0
    {
139
0
        return -1;
140
0
    }
141
142
    virtual OGRErr ISetSpatialFilter(int iGeomField,
143
                                     const OGRGeometry *poGeom) override;
144
145
    OGRErr SetAttributeFilter(const char *) override;
146
147
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
148
                      bool bForce) override;
149
};
150
151
/************************************************************************/
152
/*                           GDALEEDALayer()                            */
153
/************************************************************************/
154
155
GDALEEDALayer::GDALEEDALayer(GDALEEDADataset *poDS,
156
                             const CPLString &osCollection,
157
                             const CPLString &osCollectionName,
158
                             json_object *poAsset, json_object *poLayerConf)
159
0
    : m_poDS(poDS), m_osCollection(osCollection),
160
0
      m_osCollectionName(osCollectionName), m_poFeatureDefn(nullptr),
161
0
      m_poCurPageObj(nullptr), m_poCurPageAssets(nullptr), m_nIndexInPage(0),
162
0
      m_nFID(1), m_bFilterMustBeClientSideEvaluated(true)
163
0
{
164
0
    CPLString osLaundered(osCollection);
165
0
    for (size_t i = 0; i < osLaundered.size(); i++)
166
0
    {
167
0
        if (!isalnum(static_cast<unsigned char>(osLaundered[i])))
168
0
        {
169
0
            osLaundered[i] = '_';
170
0
        }
171
0
    }
172
0
    SetDescription(osLaundered);
173
0
    m_poFeatureDefn = new OGRFeatureDefn(osLaundered);
174
0
    m_poFeatureDefn->Reference();
175
0
    m_poFeatureDefn->SetGeomType(wkbMultiPolygon);
176
0
    OGRSpatialReference *poSRS = new OGRSpatialReference();
177
0
    poSRS->SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
178
0
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
179
0
    poSRS->Release();
180
181
0
    {
182
0
        OGRFieldDefn oFieldDefn("name", OFTString);
183
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
184
0
    }
185
0
    {
186
0
        OGRFieldDefn oFieldDefn("id", OFTString);
187
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
188
0
    }
189
0
    {
190
0
        OGRFieldDefn oFieldDefn("gdal_dataset", OFTString);
191
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
192
0
    }
193
0
    {
194
0
        OGRFieldDefn oFieldDefn("updateTime", OFTDateTime);
195
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
196
0
    }
197
0
    {
198
0
        OGRFieldDefn oFieldDefn("startTime", OFTDateTime);
199
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
200
0
    }
201
0
    {
202
0
        OGRFieldDefn oFieldDefn("endTime", OFTDateTime);
203
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
204
0
    }
205
0
    {
206
0
        OGRFieldDefn oFieldDefn("sizeBytes", OFTInteger64);
207
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
208
0
    }
209
0
    {
210
0
        OGRFieldDefn oFieldDefn("band_count", OFTInteger);
211
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
212
0
    }
213
0
    {
214
0
        OGRFieldDefn oFieldDefn("band_max_width", OFTInteger);
215
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
216
0
    }
217
0
    {
218
0
        OGRFieldDefn oFieldDefn("band_max_height", OFTInteger);
219
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
220
0
    }
221
0
    {
222
0
        OGRFieldDefn oFieldDefn("band_min_pixel_size", OFTReal);
223
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
224
0
    }
225
0
    {
226
0
        OGRFieldDefn oFieldDefn("band_upper_left_x", OFTReal);
227
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
228
0
    }
229
0
    {
230
0
        OGRFieldDefn oFieldDefn("band_upper_left_y", OFTReal);
231
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
232
0
    }
233
0
    {
234
0
        OGRFieldDefn oFieldDefn("band_crs", OFTString);
235
0
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
236
0
    }
237
238
0
    if (poLayerConf)
239
0
    {
240
0
        json_object *poFields =
241
0
            CPL_json_object_object_get(poLayerConf, "fields");
242
0
        if (poFields == nullptr ||
243
0
            json_object_get_type(poFields) != json_type_array)
244
0
        {
245
0
            CPLError(CE_Failure, CPLE_AppDefined,
246
0
                     "Cannot find %s.fields object in eedaconf.json",
247
0
                     GetDescription());
248
0
            return;
249
0
        }
250
251
0
        const auto nFields = json_object_array_length(poFields);
252
0
        for (auto i = decltype(nFields){0}; i < nFields; i++)
253
0
        {
254
0
            json_object *poField = json_object_array_get_idx(poFields, i);
255
0
            if (poField && json_object_get_type(poField) == json_type_object)
256
0
            {
257
0
                json_object *poName =
258
0
                    CPL_json_object_object_get(poField, "name");
259
0
                json_object *poType =
260
0
                    CPL_json_object_object_get(poField, "type");
261
0
                if (poName &&
262
0
                    json_object_get_type(poName) == json_type_string &&
263
0
                    poType && json_object_get_type(poType) == json_type_string)
264
0
                {
265
0
                    const char *pszName = json_object_get_string(poName);
266
0
                    const char *pszType = json_object_get_string(poType);
267
0
                    OGRFieldType eType(OFTString);
268
0
                    if (EQUAL(pszType, "datetime"))
269
0
                        eType = OFTDateTime;
270
0
                    else if (EQUAL(pszType, "double"))
271
0
                        eType = OFTReal;
272
0
                    else if (EQUAL(pszType, "int"))
273
0
                        eType = OFTInteger;
274
0
                    else if (EQUAL(pszType, "int64"))
275
0
                        eType = OFTInteger64;
276
0
                    else if (EQUAL(pszType, "string"))
277
0
                        eType = OFTString;
278
0
                    else
279
0
                    {
280
0
                        CPLError(CE_Warning, CPLE_AppDefined,
281
0
                                 "Unrecognized field type %s for field %s",
282
0
                                 pszType, pszName);
283
0
                    }
284
0
                    OGRFieldDefn oFieldDefn(pszName, eType);
285
0
                    m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
286
0
                    m_oSetQueryableFields.insert(
287
0
                        m_poFeatureDefn->GetFieldCount() - 1);
288
0
                }
289
0
            }
290
0
        }
291
292
0
        json_object *poAddOtherProp = CPL_json_object_object_get(
293
0
            poLayerConf, "add_other_properties_field");
294
0
        if (json_object_get_boolean(poAddOtherProp))
295
0
        {
296
0
            OGRFieldDefn oFieldDefn("other_properties", OFTString);
297
0
            m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
298
0
        }
299
0
    }
300
0
    else
301
0
    {
302
0
        json_object *poProperties =
303
0
            CPL_json_object_object_get(poAsset, "properties");
304
0
        if (poProperties != nullptr &&
305
0
            json_object_get_type(poProperties) == json_type_object)
306
0
        {
307
0
            json_object_iter it;
308
0
            it.key = nullptr;
309
0
            it.val = nullptr;
310
0
            it.entry = nullptr;
311
0
            json_object_object_foreachC(poProperties, it)
312
0
            {
313
0
                OGRFieldType eType(OFTString);
314
0
                if (it.val)
315
0
                {
316
0
                    if (json_object_get_type(it.val) == json_type_int)
317
0
                    {
318
0
                        if (strstr(it.key, "PERCENTAGE"))
319
0
                            eType = OFTReal;
320
0
                        else if (CPLAtoGIntBig(json_object_get_string(it.val)) >
321
0
                                 INT_MAX)
322
0
                            eType = OFTInteger64;
323
0
                        else
324
0
                            eType = OFTInteger;
325
0
                    }
326
0
                    else if (json_object_get_type(it.val) == json_type_double)
327
0
                    {
328
0
                        eType = OFTReal;
329
0
                    }
330
0
                }
331
0
                OGRFieldDefn oFieldDefn(it.key, eType);
332
0
                m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
333
0
                m_oSetQueryableFields.insert(m_poFeatureDefn->GetFieldCount() -
334
0
                                             1);
335
0
            }
336
0
        }
337
0
        {
338
0
            OGRFieldDefn oFieldDefn("other_properties", OFTString);
339
0
            m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
340
0
        }
341
0
    }
342
0
}
343
344
/************************************************************************/
345
/*                           ~GDALEEDALayer()                           */
346
/************************************************************************/
347
348
GDALEEDALayer::~GDALEEDALayer()
349
0
{
350
0
    m_poFeatureDefn->Release();
351
0
    if (m_poCurPageObj)
352
0
        json_object_put(m_poCurPageObj);
353
0
}
354
355
/************************************************************************/
356
/*                            ResetReading()                            */
357
/************************************************************************/
358
359
void GDALEEDALayer::ResetReading()
360
0
{
361
0
    if (m_poCurPageObj)
362
0
        json_object_put(m_poCurPageObj);
363
0
    m_poCurPageObj = nullptr;
364
0
    m_poCurPageAssets = nullptr;
365
0
    m_nIndexInPage = 0;
366
0
    m_nFID = 1;
367
0
}
368
369
/************************************************************************/
370
/*                         GetNextRawFeature()                          */
371
/************************************************************************/
372
373
OGRFeature *GDALEEDALayer::GetNextRawFeature()
374
0
{
375
0
    CPLString osNextPageToken;
376
0
    if (m_poCurPageAssets != nullptr &&
377
0
        m_nIndexInPage >=
378
0
            static_cast<int>(json_object_array_length(m_poCurPageAssets)))
379
0
    {
380
0
        json_object *poToken =
381
0
            CPL_json_object_object_get(m_poCurPageObj, "nextPageToken");
382
0
        const char *pszToken = json_object_get_string(poToken);
383
0
        if (pszToken == nullptr)
384
0
            return nullptr;
385
0
        osNextPageToken = pszToken;
386
0
        json_object_put(m_poCurPageObj);
387
0
        m_poCurPageObj = nullptr;
388
0
        m_poCurPageAssets = nullptr;
389
0
        m_nIndexInPage = 0;
390
0
    }
391
392
0
    if (m_poCurPageObj == nullptr)
393
0
    {
394
0
        CPLString osURL(m_poDS->GetBaseURL() + m_osCollectionName +
395
0
                        ":listImages");
396
0
        CPLString query = "";
397
0
        if (!osNextPageToken.empty())
398
0
        {
399
0
            query +=
400
0
                "&pageToken=" + CPLEscapeURLQueryParameter(osNextPageToken);
401
0
        }
402
0
        const char *pszPageSize = CPLGetConfigOption("EEDA_PAGE_SIZE", nullptr);
403
0
        if (pszPageSize)
404
0
        {
405
0
            query += "&pageSize=";
406
0
            query += pszPageSize;
407
0
        }
408
0
        if (m_poFilterGeom != nullptr)
409
0
        {
410
0
            char *pszGeoJSON =
411
0
                OGR_G_ExportToJson(OGRGeometry::ToHandle(m_poFilterGeom));
412
0
            query += "&region=";
413
0
            query += CPLEscapeURLQueryParameter(pszGeoJSON);
414
0
            CPLFree(pszGeoJSON);
415
0
        }
416
0
        if (!m_osAttributeFilter.empty())
417
0
        {
418
0
            query += "&filter=";
419
0
            query += CPLEscapeURLQueryParameter(m_osAttributeFilter);
420
0
        }
421
0
        if (!m_osStartTime.empty())
422
0
        {
423
0
            query += "&startTime=";
424
0
            query += CPLEscapeURLQueryParameter(m_osStartTime);
425
0
        }
426
0
        if (!m_osEndTime.empty())
427
0
        {
428
0
            query += "&endTime=";
429
0
            query += CPLEscapeURLQueryParameter(m_osEndTime);
430
0
        }
431
0
        if (query.size() > 0)
432
0
        {
433
0
            osURL = osURL + "?" + query.substr(1);
434
0
        }
435
0
        m_poCurPageObj = m_poDS->RunRequest(osURL);
436
0
        if (m_poCurPageObj == nullptr)
437
0
            return nullptr;
438
439
0
        m_poCurPageAssets =
440
0
            CPL_json_object_object_get(m_poCurPageObj, "images");
441
0
    }
442
443
0
    if (m_poCurPageAssets == nullptr ||
444
0
        json_object_get_type(m_poCurPageAssets) != json_type_array)
445
0
    {
446
0
        json_object_put(m_poCurPageObj);
447
0
        m_poCurPageObj = nullptr;
448
0
        return nullptr;
449
0
    }
450
0
    json_object *poAsset =
451
0
        json_object_array_get_idx(m_poCurPageAssets, m_nIndexInPage);
452
0
    if (poAsset == nullptr || json_object_get_type(poAsset) != json_type_object)
453
0
    {
454
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid asset");
455
0
        return nullptr;
456
0
    }
457
458
0
    OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
459
0
    poFeature->SetFID(m_nFID);
460
461
0
    json_object *poJSonGeom = CPL_json_object_object_get(poAsset, "geometry");
462
0
    if (poJSonGeom != nullptr &&
463
0
        json_object_get_type(poJSonGeom) == json_type_object)
464
0
    {
465
0
        const char *pszGeoJSON = json_object_get_string(poJSonGeom);
466
0
        if (strstr(pszGeoJSON, "Infinity") == nullptr)
467
0
        {
468
0
            OGRGeometry *poGeom = OGRGeometry::FromHandle(
469
0
                OGR_G_CreateGeometryFromJson(pszGeoJSON));
470
0
            if (poGeom != nullptr)
471
0
            {
472
0
                if (poGeom->getGeometryType() == wkbPolygon)
473
0
                {
474
0
                    OGRMultiPolygon *poMP = new OGRMultiPolygon();
475
0
                    poMP->addGeometryDirectly(poGeom);
476
0
                    poGeom = poMP;
477
0
                }
478
0
                poGeom->assignSpatialReference(
479
0
                    m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
480
0
                poFeature->SetGeometryDirectly(poGeom);
481
0
            }
482
0
        }
483
0
    }
484
485
0
    const char *pszName =
486
0
        json_object_get_string(CPL_json_object_object_get(poAsset, "name"));
487
0
    if (pszName)
488
0
    {
489
0
        poFeature->SetField("name", pszName);
490
0
        poFeature->SetField("gdal_dataset",
491
0
                            ("EEDAI:" + CPLString(pszName)).c_str());
492
0
    }
493
494
0
    const char *pszId =
495
0
        json_object_get_string(CPL_json_object_object_get(poAsset, "id"));
496
0
    if (pszId)
497
0
    {
498
0
        poFeature->SetField("id", pszId);
499
0
    }
500
501
0
    const char *const apszBaseProps[] = {"updateTime", "startTime", "endTime",
502
0
                                         "sizeBytes"};
503
0
    for (size_t i = 0; i < CPL_ARRAYSIZE(apszBaseProps); i++)
504
0
    {
505
0
        const char *pszVal = json_object_get_string(
506
0
            CPL_json_object_object_get(poAsset, apszBaseProps[i]));
507
0
        if (pszVal)
508
0
        {
509
0
            poFeature->SetField(apszBaseProps[i], pszVal);
510
0
        }
511
0
    }
512
513
0
    json_object *poBands = CPL_json_object_object_get(poAsset, "bands");
514
0
    if (poBands != nullptr && json_object_get_type(poBands) == json_type_array)
515
0
    {
516
0
        std::vector<EEDAIBandDesc> aoBands =
517
0
            BuildBandDescArray(poBands, m_oMapCodeToWKT);
518
0
        poFeature->SetField("band_count", static_cast<int>(aoBands.size()));
519
0
        if (!aoBands.empty())
520
0
        {
521
0
            int nWidth = 0, nHeight = 0;
522
0
            double dfMinPixelSize = std::numeric_limits<double>::max();
523
0
            CPLString osSRS(aoBands[0].osWKT);
524
0
            double dfULX = aoBands[0].gt.xorig;
525
0
            double dfULY = aoBands[0].gt.yorig;
526
0
            bool bULValid = true;
527
0
            for (size_t i = 0; i < aoBands.size(); i++)
528
0
            {
529
0
                nWidth = std::max(nWidth, aoBands[i].nWidth);
530
0
                nHeight = std::max(nHeight, aoBands[i].nHeight);
531
0
                dfMinPixelSize = std::min(
532
0
                    dfMinPixelSize,
533
0
                    std::min(aoBands[i].gt.xscale, fabs(aoBands[i].gt.yscale)));
534
0
                if (osSRS != aoBands[i].osWKT)
535
0
                {
536
0
                    osSRS.clear();
537
0
                }
538
0
                if (dfULX != aoBands[i].gt.xorig ||
539
0
                    dfULY != aoBands[i].gt.yorig)
540
0
                {
541
0
                    bULValid = false;
542
0
                }
543
0
            }
544
0
            poFeature->SetField("band_max_width", nWidth);
545
0
            poFeature->SetField("band_max_height", nHeight);
546
0
            poFeature->SetField("band_min_pixel_size", dfMinPixelSize);
547
0
            if (bULValid)
548
0
            {
549
0
                poFeature->SetField("band_upper_left_x", dfULX);
550
0
                poFeature->SetField("band_upper_left_y", dfULY);
551
0
            }
552
0
            if (!osSRS.empty())
553
0
            {
554
0
                OGRSpatialReference oSRS;
555
0
                oSRS.SetFromUserInput(
556
0
                    osSRS,
557
0
                    OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
558
0
                const char *pszAuthName = oSRS.GetAuthorityName();
559
0
                const char *pszAuthCode = oSRS.GetAuthorityCode();
560
0
                if (pszAuthName && pszAuthCode)
561
0
                {
562
0
                    poFeature->SetField(
563
0
                        "band_crs",
564
0
                        CPLSPrintf("%s:%s", pszAuthName, pszAuthCode));
565
0
                }
566
0
                else
567
0
                {
568
0
                    poFeature->SetField("band_crs", osSRS.c_str());
569
0
                }
570
0
            }
571
0
        }
572
0
    }
573
574
0
    json_object *poProperties =
575
0
        CPL_json_object_object_get(poAsset, "properties");
576
0
    if (poProperties != nullptr &&
577
0
        json_object_get_type(poProperties) == json_type_object)
578
0
    {
579
0
        json_object *poOtherProperties = nullptr;
580
581
0
        json_object_iter it;
582
0
        it.key = nullptr;
583
0
        it.val = nullptr;
584
0
        it.entry = nullptr;
585
0
        json_object_object_foreachC(poProperties, it)
586
0
        {
587
0
            if (it.val)
588
0
            {
589
0
                int nIdx = m_poFeatureDefn->GetFieldIndex(it.key);
590
0
                if (nIdx >= 0)
591
0
                {
592
0
                    poFeature->SetField(nIdx, json_object_get_string(it.val));
593
0
                }
594
0
                else
595
0
                {
596
0
                    if (poOtherProperties == nullptr)
597
0
                        poOtherProperties = json_object_new_object();
598
0
                    json_object_object_add(poOtherProperties, it.key, it.val);
599
0
                    json_object_get(it.val);
600
0
                }
601
0
            }
602
0
        }
603
604
0
        if (poOtherProperties)
605
0
        {
606
0
            int nIdxOtherProperties =
607
0
                m_poFeatureDefn->GetFieldIndex("other_properties");
608
0
            if (nIdxOtherProperties >= 0)
609
0
            {
610
0
                poFeature->SetField(nIdxOtherProperties,
611
0
                                    json_object_get_string(poOtherProperties));
612
0
            }
613
0
            json_object_put(poOtherProperties);
614
0
        }
615
0
    }
616
617
0
    m_nFID++;
618
0
    m_nIndexInPage++;
619
620
0
    return poFeature;
621
0
}
622
623
/************************************************************************/
624
/*                           GetNextFeature()                           */
625
/************************************************************************/
626
627
OGRFeature *GDALEEDALayer::GetNextFeature()
628
0
{
629
0
    while (true)
630
0
    {
631
0
        OGRFeature *poFeature = GetNextRawFeature();
632
0
        if (poFeature == nullptr)
633
0
            return nullptr;
634
635
0
        if (m_poAttrQuery == nullptr || !m_bFilterMustBeClientSideEvaluated ||
636
0
            m_poAttrQuery->Evaluate(poFeature))
637
0
        {
638
0
            return poFeature;
639
0
        }
640
0
        else
641
0
        {
642
0
            delete poFeature;
643
0
        }
644
0
    }
645
0
}
646
647
/************************************************************************/
648
/*                     GDALEEDALayerParseDateTime()                     */
649
/************************************************************************/
650
651
static int GDALEEDALayerParseDateTime(const char *pszValue, int operation,
652
                                      int &nYear, int &nMonth, int &nDay,
653
                                      int &nHour, int &nMinute, int &nSecond)
654
0
{
655
0
    nHour = (operation == SWQ_GE) ? 0 : 23;
656
0
    nMinute = (operation == SWQ_GE) ? 0 : 59;
657
0
    nSecond = (operation == SWQ_GE) ? 0 : 59;
658
0
    int nRet = sscanf(pszValue, "%04d/%02d/%02d %02d:%02d:%02d", &nYear,
659
0
                      &nMonth, &nDay, &nHour, &nMinute, &nSecond);
660
0
    if (nRet >= 3)
661
0
    {
662
0
        return nRet;
663
0
    }
664
0
    nRet = sscanf(pszValue, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth,
665
0
                  &nDay, &nHour, &nMinute, &nSecond);
666
0
    if (nRet >= 3)
667
0
    {
668
0
        return nRet;
669
0
    }
670
0
    return 0;
671
0
}
672
673
/************************************************************************/
674
/*                         IsSimpleComparison()                         */
675
/************************************************************************/
676
677
bool GDALEEDALayer::IsSimpleComparison(const swq_expr_node *poNode)
678
0
{
679
0
    return poNode->eNodeType == SNT_OPERATION &&
680
0
           (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_NE ||
681
0
            poNode->nOperation == SWQ_LT || poNode->nOperation == SWQ_LE ||
682
0
            poNode->nOperation == SWQ_GT || poNode->nOperation == SWQ_GE) &&
683
0
           poNode->nSubExprCount == 2 &&
684
0
           poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
685
0
           poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
686
0
           m_oSetQueryableFields.find(poNode->papoSubExpr[0]->field_index) !=
687
0
               m_oSetQueryableFields.end();
688
0
}
689
690
/************************************************************************/
691
/*                          GetOperatorText()                           */
692
/************************************************************************/
693
694
static const char *GetOperatorText(swq_op nOp)
695
0
{
696
0
    if (nOp == SWQ_LT)
697
0
        return "<";
698
0
    if (nOp == SWQ_LE)
699
0
        return "<=";
700
0
    if (nOp == SWQ_GT)
701
0
        return ">";
702
0
    if (nOp == SWQ_GE)
703
0
        return ">=";
704
0
    if (nOp == SWQ_EQ)
705
0
        return "=";
706
0
    if (nOp == SWQ_NE)
707
0
        return "!=";
708
0
    CPLAssert(false);
709
0
    return "";
710
0
}
711
712
/************************************************************************/
713
/*                            BuildFilter()                             */
714
/************************************************************************/
715
716
CPLString GDALEEDALayer::BuildFilter(swq_expr_node *poNode, bool bIsAndTopLevel)
717
0
{
718
0
    int nYear, nMonth, nDay, nHour, nMinute, nSecond;
719
720
0
    if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
721
0
        poNode->nSubExprCount == 2)
722
0
    {
723
        // For AND, we can deal with a failure in one of the branch
724
        // since client-side will do that extra filtering
725
0
        CPLString osLeft = BuildFilter(poNode->papoSubExpr[0], bIsAndTopLevel);
726
0
        CPLString osRight = BuildFilter(poNode->papoSubExpr[1], bIsAndTopLevel);
727
0
        if (!osLeft.empty() && !osRight.empty())
728
0
        {
729
0
            return "(" + osLeft + " AND " + osRight + ")";
730
0
        }
731
0
        else if (!osLeft.empty())
732
0
            return osLeft;
733
0
        else
734
0
            return osRight;
735
0
    }
736
0
    else if (poNode->eNodeType == SNT_OPERATION &&
737
0
             poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2)
738
0
    {
739
        // For OR, we need both members to be valid
740
0
        CPLString osLeft = BuildFilter(poNode->papoSubExpr[0], false);
741
0
        CPLString osRight = BuildFilter(poNode->papoSubExpr[1], false);
742
0
        if (!osLeft.empty() && !osRight.empty())
743
0
        {
744
0
            return "(" + osLeft + " OR " + osRight + ")";
745
0
        }
746
0
        return "";
747
0
    }
748
0
    else if (poNode->eNodeType == SNT_OPERATION &&
749
0
             poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1)
750
0
    {
751
0
        CPLString osFilter = BuildFilter(poNode->papoSubExpr[0], false);
752
0
        if (!osFilter.empty())
753
0
        {
754
0
            return "(NOT " + osFilter + ")";
755
0
        }
756
0
        return "";
757
0
    }
758
0
    else if (IsSimpleComparison(poNode))
759
0
    {
760
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
761
0
        CPLString osFilter(
762
0
            m_poFeatureDefn->GetFieldDefn(nFieldIdx)->GetNameRef());
763
0
        osFilter += " ";
764
0
        osFilter += GetOperatorText(poNode->nOperation);
765
0
        osFilter += " ";
766
0
        if (poNode->papoSubExpr[1]->field_type == SWQ_INTEGER ||
767
0
            poNode->papoSubExpr[1]->field_type == SWQ_INTEGER64)
768
0
        {
769
0
            osFilter +=
770
0
                CPLSPrintf("%" PRId64, poNode->papoSubExpr[1]->int_value);
771
0
        }
772
0
        else if (poNode->papoSubExpr[1]->field_type == SWQ_FLOAT)
773
0
        {
774
0
            osFilter +=
775
0
                CPLSPrintf("%.17g", poNode->papoSubExpr[1]->float_value);
776
0
        }
777
0
        else
778
0
        {
779
0
            osFilter += "\"";
780
0
            osFilter += poNode->papoSubExpr[1]->string_value;
781
0
            osFilter += "\"";
782
0
        }
783
0
        return osFilter;
784
0
    }
785
0
    else if (bIsAndTopLevel && poNode->eNodeType == SNT_OPERATION &&
786
0
             (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_GE) &&
787
0
             poNode->nSubExprCount == 2 &&
788
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
789
0
             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
790
0
             poNode->papoSubExpr[0]->field_index ==
791
0
                 m_poFeatureDefn->GetFieldIndex("startTime") &&
792
0
             poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
793
0
    {
794
0
        int nTerms = GDALEEDALayerParseDateTime(
795
0
            poNode->papoSubExpr[1]->string_value, SWQ_GE, nYear, nMonth, nDay,
796
0
            nHour, nMinute, nSecond);
797
0
        if (nTerms >= 3)
798
0
        {
799
0
            m_osStartTime = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
800
0
                                       nMonth, nDay, nHour, nMinute, nSecond);
801
0
        }
802
0
        else
803
0
        {
804
0
            m_bFilterMustBeClientSideEvaluated = true;
805
0
        }
806
0
        return "";
807
0
    }
808
0
    else if (bIsAndTopLevel && poNode->eNodeType == SNT_OPERATION &&
809
0
             (poNode->nOperation == SWQ_EQ || poNode->nOperation == SWQ_LE) &&
810
0
             poNode->nSubExprCount == 2 &&
811
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
812
0
             poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT &&
813
0
             poNode->papoSubExpr[0]->field_index ==
814
0
                 m_poFeatureDefn->GetFieldIndex("endTime") &&
815
0
             poNode->papoSubExpr[1]->field_type == SWQ_TIMESTAMP)
816
0
    {
817
0
        int nTerms = GDALEEDALayerParseDateTime(
818
0
            poNode->papoSubExpr[1]->string_value, SWQ_LE, nYear, nMonth, nDay,
819
0
            nHour, nMinute, nSecond);
820
0
        if (nTerms >= 3)
821
0
        {
822
0
            if (poNode->nOperation == SWQ_EQ && nTerms == 6)
823
0
            {
824
0
                if (nSecond < 59)
825
0
                    nSecond++;
826
0
                else if (nMinute < 59)
827
0
                    nMinute++;
828
0
                else if (nHour < 23)
829
0
                    nHour++;
830
0
                else
831
0
                    nDay++;
832
0
            }
833
0
            m_osEndTime = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
834
0
                                     nMonth, nDay, nHour, nMinute, nSecond);
835
0
        }
836
0
        else
837
0
        {
838
0
            m_bFilterMustBeClientSideEvaluated = true;
839
0
        }
840
0
        return "";
841
0
    }
842
0
    else if (poNode->eNodeType == SNT_OPERATION &&
843
0
             poNode->nOperation == SWQ_IN && poNode->nSubExprCount >= 2 &&
844
0
             poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN &&
845
0
             m_oSetQueryableFields.find(poNode->papoSubExpr[0]->field_index) !=
846
0
                 m_oSetQueryableFields.end())
847
0
    {
848
0
        const int nFieldIdx = poNode->papoSubExpr[0]->field_index;
849
0
        CPLString osFilter;
850
851
0
        for (int i = 1; i < poNode->nSubExprCount; i++)
852
0
        {
853
0
            if (!osFilter.empty())
854
0
                osFilter += " OR ";
855
0
            osFilter += m_poFeatureDefn->GetFieldDefn(nFieldIdx)->GetNameRef();
856
0
            osFilter += " = ";
857
0
            if (poNode->papoSubExpr[i]->field_type == SWQ_INTEGER ||
858
0
                poNode->papoSubExpr[i]->field_type == SWQ_INTEGER64)
859
0
            {
860
0
                osFilter +=
861
0
                    CPLSPrintf("%" PRId64, poNode->papoSubExpr[i]->int_value);
862
0
            }
863
0
            else if (poNode->papoSubExpr[i]->field_type == SWQ_FLOAT)
864
0
            {
865
0
                osFilter +=
866
0
                    CPLSPrintf("%.17g", poNode->papoSubExpr[i]->float_value);
867
0
            }
868
0
            else
869
0
            {
870
0
                osFilter += "\"";
871
0
                osFilter += poNode->papoSubExpr[i]->string_value;
872
0
                osFilter += "\"";
873
0
            }
874
0
        }
875
876
0
        return osFilter;
877
0
    }
878
879
0
    m_bFilterMustBeClientSideEvaluated = true;
880
0
    return "";
881
0
}
882
883
/************************************************************************/
884
/*                         SetAttributeFilter()                         */
885
/************************************************************************/
886
887
OGRErr GDALEEDALayer::SetAttributeFilter(const char *pszQuery)
888
889
0
{
890
0
    m_osAttributeFilter.clear();
891
0
    m_osStartTime.clear();
892
0
    m_osEndTime.clear();
893
0
    m_bFilterMustBeClientSideEvaluated = false;
894
895
0
    if (pszQuery && STARTS_WITH_CI(pszQuery, "EEDA:"))
896
0
    {
897
0
        m_osAttributeFilter = pszQuery + strlen("EEDA:");
898
0
        OGRLayer::SetAttributeFilter(nullptr);
899
0
        ResetReading();
900
0
        return OGRERR_NONE;
901
0
    }
902
903
0
    OGRErr eErr = OGRLayer::SetAttributeFilter(pszQuery);
904
905
0
    if (m_poAttrQuery != nullptr)
906
0
    {
907
0
        swq_expr_node *poNode =
908
0
            static_cast<swq_expr_node *>(m_poAttrQuery->GetSWQExpr());
909
910
0
#ifndef PLUGIN
911
0
        poNode->ReplaceBetweenByGEAndLERecurse();
912
0
#endif
913
914
0
        m_osAttributeFilter = BuildFilter(poNode, true);
915
0
        if (m_osAttributeFilter.empty() && m_osStartTime.empty() &&
916
0
            m_osEndTime.empty())
917
0
        {
918
0
            CPLDebug("EEDA", "Full filter will be evaluated on client side.");
919
0
        }
920
0
        else if (m_bFilterMustBeClientSideEvaluated)
921
0
        {
922
0
            CPLDebug(
923
0
                "EEDA",
924
0
                "Only part of the filter will be evaluated on server side.");
925
0
        }
926
0
    }
927
928
0
    ResetReading();
929
930
0
    return eErr;
931
0
}
932
933
/************************************************************************/
934
/*                         ISetSpatialFilter()                          */
935
/************************************************************************/
936
937
OGRErr GDALEEDALayer::ISetSpatialFilter(int /* iGeomField */,
938
                                        const OGRGeometry *poGeomIn)
939
0
{
940
0
    if (poGeomIn)
941
0
    {
942
0
        OGREnvelope sEnvelope;
943
0
        poGeomIn->getEnvelope(&sEnvelope);
944
0
        if (sEnvelope.MinX == sEnvelope.MaxX &&
945
0
            sEnvelope.MinY == sEnvelope.MaxY)
946
0
        {
947
0
            OGRPoint p(sEnvelope.MinX, sEnvelope.MinY);
948
0
            InstallFilter(&p);
949
0
        }
950
0
        else
951
0
            InstallFilter(poGeomIn);
952
0
    }
953
0
    else
954
0
        InstallFilter(poGeomIn);
955
956
0
    ResetReading();
957
0
    return OGRERR_NONE;
958
0
}
959
960
/************************************************************************/
961
/*                             GetExtent()                              */
962
/************************************************************************/
963
964
OGRErr GDALEEDALayer::IGetExtent(int /* iGeomField*/, OGREnvelope *psExtent,
965
                                 bool /* bForce */)
966
0
{
967
0
    psExtent->MinX = -180;
968
0
    psExtent->MinY = -90;
969
0
    psExtent->MaxX = 180;
970
0
    psExtent->MaxY = 90;
971
0
    return OGRERR_NONE;
972
0
}
973
974
/************************************************************************/
975
/*                           TestCapability()                           */
976
/************************************************************************/
977
978
int GDALEEDALayer::TestCapability(const char *pszCap) const
979
0
{
980
0
    if (EQUAL(pszCap, OLCStringsAsUTF8))
981
0
        return TRUE;
982
0
    return FALSE;
983
0
}
984
985
/************************************************************************/
986
/*                          GDALEEDADataset()                           */
987
/************************************************************************/
988
989
86
GDALEEDADataset::GDALEEDADataset() : m_poLayer(nullptr)
990
86
{
991
86
}
992
993
/************************************************************************/
994
/*                          ~GDALEEDADataset()                          */
995
/************************************************************************/
996
997
GDALEEDADataset::~GDALEEDADataset()
998
86
{
999
86
    delete m_poLayer;
1000
86
}
1001
1002
/************************************************************************/
1003
/*                              GetLayer()                              */
1004
/************************************************************************/
1005
1006
const OGRLayer *GDALEEDADataset::GetLayer(int idx) const
1007
0
{
1008
0
    if (idx == 0)
1009
0
        return m_poLayer;
1010
0
    return nullptr;
1011
0
}
1012
1013
/************************************************************************/
1014
/*                             RunRequest()                             */
1015
/************************************************************************/
1016
1017
json_object *GDALEEDADataset::RunRequest(const CPLString &osURL)
1018
86
{
1019
86
    char **papszOptions = GetBaseHTTPOptions();
1020
86
    if (papszOptions == nullptr)
1021
0
        return nullptr;
1022
86
    CPLHTTPResult *psResult = EEDAHTTPFetch(osURL, papszOptions);
1023
86
    CSLDestroy(papszOptions);
1024
86
    if (psResult == nullptr)
1025
0
        return nullptr;
1026
86
    if (psResult->pszErrBuf != nullptr)
1027
86
    {
1028
86
        CPLError(CE_Failure, CPLE_AppDefined, "%s",
1029
86
                 psResult->pabyData
1030
86
                     ? reinterpret_cast<const char *>(psResult->pabyData)
1031
86
                     : psResult->pszErrBuf);
1032
86
        CPLHTTPDestroyResult(psResult);
1033
86
        return nullptr;
1034
86
    }
1035
1036
0
    if (psResult->pabyData == nullptr)
1037
0
    {
1038
0
        CPLError(CE_Failure, CPLE_AppDefined,
1039
0
                 "Empty content returned by server");
1040
0
        CPLHTTPDestroyResult(psResult);
1041
0
        return nullptr;
1042
0
    }
1043
1044
0
    const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
1045
#ifdef DEBUG_VERBOSE
1046
    CPLDebug("EEDA", "%s", pszText);
1047
#endif
1048
1049
0
    json_object *poObj = nullptr;
1050
0
    if (!OGRJSonParse(pszText, &poObj, true))
1051
0
    {
1052
0
        CPLHTTPDestroyResult(psResult);
1053
0
        return nullptr;
1054
0
    }
1055
1056
0
    CPLHTTPDestroyResult(psResult);
1057
1058
0
    if (json_object_get_type(poObj) != json_type_object)
1059
0
    {
1060
0
        CPLError(CE_Failure, CPLE_AppDefined,
1061
0
                 "Return is not a JSON dictionary");
1062
0
        json_object_put(poObj);
1063
0
        return nullptr;
1064
0
    }
1065
1066
0
    return poObj;
1067
0
}
1068
1069
/************************************************************************/
1070
/*                       GDALEEDADatasetGetConf()                       */
1071
/************************************************************************/
1072
1073
static json_object *GDALEEDADatasetGetConf()
1074
86
{
1075
86
    const char *pszConfFile = CPLFindFile("gdal", "eedaconf.json");
1076
86
    if (pszConfFile == nullptr)
1077
86
    {
1078
86
        CPLDebug("EEDA", "Cannot find eedaconf.json");
1079
86
        return nullptr;
1080
86
    }
1081
1082
0
    GByte *pabyRet = nullptr;
1083
0
    if (!VSIIngestFile(nullptr, pszConfFile, &pabyRet, nullptr, -1))
1084
0
    {
1085
0
        return nullptr;
1086
0
    }
1087
1088
0
    json_object *poRoot = nullptr;
1089
0
    const char *pzText = reinterpret_cast<char *>(pabyRet);
1090
0
    if (!OGRJSonParse(pzText, &poRoot))
1091
0
    {
1092
0
        VSIFree(pabyRet);
1093
0
        return nullptr;
1094
0
    }
1095
0
    VSIFree(pabyRet);
1096
1097
0
    if (json_object_get_type(poRoot) != json_type_object)
1098
0
    {
1099
0
        json_object_put(poRoot);
1100
0
        return nullptr;
1101
0
    }
1102
1103
0
    return poRoot;
1104
0
}
1105
1106
/************************************************************************/
1107
/*                                Open()                                */
1108
/************************************************************************/
1109
1110
bool GDALEEDADataset::Open(GDALOpenInfo *poOpenInfo)
1111
86
{
1112
86
    m_osBaseURL = CPLGetConfigOption(
1113
86
        "EEDA_URL", "https://earthengine-highvolume.googleapis.com/v1alpha/");
1114
1115
86
    CPLString osCollection =
1116
86
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "COLLECTION", "");
1117
86
    if (osCollection.empty())
1118
86
    {
1119
86
        char **papszTokens =
1120
86
            CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
1121
86
        if (CSLCount(papszTokens) < 2)
1122
0
        {
1123
0
            CPLError(CE_Failure, CPLE_AppDefined,
1124
0
                     "No collection specified in connection string or "
1125
0
                     "COLLECTION open option");
1126
0
            CSLDestroy(papszTokens);
1127
0
            return false;
1128
0
        }
1129
86
        osCollection = papszTokens[1];
1130
86
        CSLDestroy(papszTokens);
1131
86
    }
1132
86
    CPLString osCollectionName = ConvertPathToName(osCollection);
1133
1134
86
    json_object *poRootConf = GDALEEDADatasetGetConf();
1135
86
    if (poRootConf)
1136
0
    {
1137
0
        json_object *poLayerConf =
1138
0
            CPL_json_object_object_get(poRootConf, osCollection);
1139
0
        if (poLayerConf != nullptr &&
1140
0
            json_object_get_type(poLayerConf) == json_type_object)
1141
0
        {
1142
0
            m_poLayer = new GDALEEDALayer(this, osCollection, osCollectionName,
1143
0
                                          nullptr, poLayerConf);
1144
0
            json_object_put(poRootConf);
1145
0
            return true;
1146
0
        }
1147
0
        json_object_put(poRootConf);
1148
0
    }
1149
1150
    // Issue request to get layer schema
1151
86
    json_object *poRootAsset =
1152
86
        RunRequest(m_osBaseURL + osCollectionName + ":listImages?pageSize=1");
1153
86
    if (poRootAsset == nullptr)
1154
86
        return false;
1155
1156
0
    json_object *poAssets = CPL_json_object_object_get(poRootAsset, "images");
1157
0
    if (poAssets == nullptr ||
1158
0
        json_object_get_type(poAssets) != json_type_array ||
1159
0
        json_object_array_length(poAssets) != 1)
1160
0
    {
1161
0
        CPLError(CE_Failure, CPLE_AppDefined, "No assets");
1162
0
        json_object_put(poRootAsset);
1163
0
        return false;
1164
0
    }
1165
0
    json_object *poAsset = json_object_array_get_idx(poAssets, 0);
1166
0
    if (poAsset == nullptr || json_object_get_type(poAsset) != json_type_object)
1167
0
    {
1168
0
        CPLError(CE_Failure, CPLE_AppDefined, "No assets");
1169
0
        json_object_put(poRootAsset);
1170
0
        return false;
1171
0
    }
1172
1173
0
    m_poLayer = new GDALEEDALayer(this, osCollection, osCollectionName, poAsset,
1174
0
                                  nullptr);
1175
0
    json_object_put(poRootAsset);
1176
1177
0
    return true;
1178
0
}
1179
1180
/************************************************************************/
1181
/*                          GDALEEDAdentify()                           */
1182
/************************************************************************/
1183
1184
static int GDALEEDAdentify(GDALOpenInfo *poOpenInfo)
1185
216k
{
1186
216k
    return STARTS_WITH_CI(poOpenInfo->pszFilename, "EEDA:");
1187
216k
}
1188
1189
/************************************************************************/
1190
/*                            GDALEEDAOpen()                            */
1191
/************************************************************************/
1192
1193
static GDALDataset *GDALEEDAOpen(GDALOpenInfo *poOpenInfo)
1194
86
{
1195
86
    if (!GDALEEDAdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
1196
0
        return nullptr;
1197
1198
86
    GDALEEDADataset *poDS = new GDALEEDADataset();
1199
86
    if (!poDS->Open(poOpenInfo))
1200
86
    {
1201
86
        delete poDS;
1202
86
        return nullptr;
1203
86
    }
1204
0
    return poDS;
1205
86
}
1206
1207
/************************************************************************/
1208
/*                         GDALRegister_EEDA()                          */
1209
/************************************************************************/
1210
1211
void GDALRegister_EEDA()
1212
1213
22
{
1214
22
    if (GDALGetDriverByName("EEDA") != nullptr)
1215
0
        return;
1216
1217
22
    GDALDriver *poDriver = new GDALDriver();
1218
1219
22
    poDriver->SetDescription("EEDA");
1220
22
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1221
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Earth Engine Data API");
1222
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/eeda.html");
1223
22
    poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "EEDA:");
1224
22
    poDriver->SetMetadataItem(
1225
22
        GDAL_DMD_OPENOPTIONLIST,
1226
22
        "<OpenOptionList>"
1227
22
        "  <Option name='COLLECTION' type='string' "
1228
22
        "description='Collection name'/>"
1229
22
        "  <Option name='VSI_PATH_FOR_AUTH' type='string' "
1230
22
        "description='/vsigs/... path onto which a "
1231
22
        "GOOGLE_APPLICATION_CREDENTIALS path specific "
1232
22
        "option is set'/>"
1233
22
        "</OpenOptionList>");
1234
1235
22
    poDriver->pfnOpen = GDALEEDAOpen;
1236
22
    poDriver->pfnIdentify = GDALEEDAdentify;
1237
1238
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1239
1240
#ifdef GDAL_ENABLE_DRIVER_EEDA_PLUGIN
1241
    GDALRegister_EEDAI();
1242
#endif
1243
22
}