Coverage Report

Created: 2025-08-11 09:23

/src/gdal/ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/*******************************************************************************
2
 *  Project: NextGIS Web Driver
3
 *  Purpose: Implements NextGIS Web Driver
4
 *  Author: Dmitry Baryshnikov, dmitry.baryshnikov@nextgis.com
5
 *  Language: C++
6
 *******************************************************************************
7
 *  The MIT License (MIT)
8
 *
9
 *  Copyright (c) 2018-2025, NextGIS <info@nextgis.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 *******************************************************************************/
13
14
#include "ogr_ngw.h"
15
16
#include "cpl_http.h"
17
#include "gdal_proxy.h"
18
19
#include <array>
20
#include <limits>
21
22
class NGWWrapperRasterBand : public GDALProxyRasterBand
23
{
24
    GDALRasterBand *poBaseBand;
25
26
  protected:
27
    virtual GDALRasterBand *
28
    RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
29
30
  public:
31
    explicit NGWWrapperRasterBand(GDALRasterBand *poBaseBandIn)
32
0
        : poBaseBand(poBaseBandIn)
33
0
    {
34
0
        eDataType = poBaseBand->GetRasterDataType();
35
0
        poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
36
0
    }
37
};
38
39
GDALRasterBand *
40
NGWWrapperRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
41
0
{
42
0
    return poBaseBand;
43
0
}
44
45
static const char *FormGDALTMSConnectionString(const std::string &osUrl,
46
                                               const std::string &osResourceId,
47
                                               int nEPSG, int nCacheExpires,
48
                                               int nCacheMaxSize)
49
0
{
50
0
    std::string osRasterUrl = NGWAPI::GetTMSURL(osUrl, osResourceId);
51
0
    char *pszRasterUrl = CPLEscapeString(osRasterUrl.c_str(), -1, CPLES_XML);
52
0
    const char *pszConnStr =
53
0
        CPLSPrintf("<GDAL_WMS><Service name=\"TMS\">"
54
0
                   "<ServerUrl>%s</ServerUrl></Service><DataWindow>"
55
0
                   "<UpperLeftX>-20037508.34</"
56
0
                   "UpperLeftX><UpperLeftY>20037508.34</UpperLeftY>"
57
0
                   "<LowerRightX>20037508.34</"
58
0
                   "LowerRightX><LowerRightY>-20037508.34</LowerRightY>"
59
0
                   "<TileLevel>%d</TileLevel><TileCountX>1</TileCountX>"
60
0
                   "<TileCountY>1</TileCountY><YOrigin>top</YOrigin></"
61
0
                   "DataWindow>"
62
0
                   "<Projection>EPSG:%d</Projection><BlockSizeX>256</"
63
0
                   "BlockSizeX>"
64
0
                   "<BlockSizeY>256</BlockSizeY><BandsCount>%d</BandsCount>"
65
0
                   "<Cache><Type>file</Type><Expires>%d</Expires><MaxSize>%d</"
66
0
                   "MaxSize>"
67
0
                   "</Cache><ZeroBlockHttpCodes>204,404</ZeroBlockHttpCodes></"
68
0
                   "GDAL_WMS>",
69
0
                   pszRasterUrl,
70
0
                   22,     // NOTE: We have no limit in zoom levels.
71
0
                   nEPSG,  // NOTE: Default SRS is EPSG:3857.
72
0
                   4, nCacheExpires, nCacheMaxSize);
73
74
0
    CPLFree(pszRasterUrl);
75
0
    return pszConnStr;
76
0
}
77
78
static std::string GetStylesIdentifiers(const CPLJSONArray &aoStyles, int nDeep)
79
0
{
80
0
    std::string sOut;
81
0
    if (nDeep > 255)
82
0
    {
83
0
        return sOut;
84
0
    }
85
86
0
    for (const auto &subobj : aoStyles)
87
0
    {
88
0
        auto sType = subobj.GetString("item_type");
89
0
        if (sType == "layer")
90
0
        {
91
0
            auto sId = subobj.GetString("layer_style_id");
92
0
            if (!sId.empty())
93
0
            {
94
0
                if (sOut.empty())
95
0
                {
96
0
                    sOut = std::move(sId);
97
0
                }
98
0
                else
99
0
                {
100
0
                    sOut += "," + sId;
101
0
                }
102
0
            }
103
0
        }
104
0
        else
105
0
        {
106
0
            auto aoChildren = subobj.GetArray("children");
107
0
            auto sId = GetStylesIdentifiers(aoChildren, nDeep + 1);
108
0
            if (!sId.empty())
109
0
            {
110
0
                if (sOut.empty())
111
0
                {
112
0
                    sOut = std::move(sId);
113
0
                }
114
0
                else
115
0
                {
116
0
                    sOut += "," + sId;
117
0
                }
118
0
            }
119
0
        }
120
0
    }
121
0
    return sOut;
122
0
}
123
124
/*
125
 * OGRNGWDataset()
126
 */
127
OGRNGWDataset::OGRNGWDataset()
128
353
    : nBatchSize(-1), nPageSize(-1), bFetchedPermissions(false),
129
353
      bHasFeaturePaging(false), bExtInNativeData(false), bMetadataDerty(false),
130
353
      poRasterDS(nullptr), nRasters(0), nCacheExpires(604800),  // 7 days
131
353
      nCacheMaxSize(67108864),                                  // 64 MB
132
353
      osJsonDepth("32")
133
353
{
134
353
}
135
136
/*
137
 * ~OGRNGWDataset()
138
 */
139
OGRNGWDataset::~OGRNGWDataset()
140
353
{
141
    // Last sync with server.
142
353
    OGRNGWDataset::FlushCache(true);
143
144
353
    if (poRasterDS != nullptr)
145
0
    {
146
0
        GDALClose(poRasterDS);
147
0
        poRasterDS = nullptr;
148
0
    }
149
353
}
150
151
/*
152
 * FetchPermissions()
153
 */
154
void OGRNGWDataset::FetchPermissions()
155
0
{
156
0
    if (bFetchedPermissions)
157
0
    {
158
0
        return;
159
0
    }
160
161
0
    if (IsUpdateMode())
162
0
    {
163
        // Check connection and is it read only.
164
0
        stPermissions = NGWAPI::CheckPermissions(
165
0
            osUrl, osResourceId, GetHeaders(false), IsUpdateMode());
166
0
    }
167
0
    else
168
0
    {
169
0
        stPermissions.bDataCanRead = true;
170
0
        stPermissions.bResourceCanRead = true;
171
0
        stPermissions.bDatastructCanRead = true;
172
0
        stPermissions.bMetadataCanRead = true;
173
0
    }
174
0
    bFetchedPermissions = true;
175
0
}
176
177
/*
178
 * TestCapability()
179
 */
180
int OGRNGWDataset::TestCapability(const char *pszCap)
181
0
{
182
0
    FetchPermissions();
183
0
    if (EQUAL(pszCap, ODsCCreateLayer))
184
0
    {
185
0
        return stPermissions.bResourceCanCreate;
186
0
    }
187
0
    else if (EQUAL(pszCap, ODsCDeleteLayer))
188
0
    {
189
0
        return stPermissions.bResourceCanDelete;
190
0
    }
191
0
    else if (EQUAL(pszCap, "RenameLayer"))
192
0
    {
193
0
        return stPermissions.bResourceCanUpdate;
194
0
    }
195
0
    else if (EQUAL(pszCap, ODsCRandomLayerWrite))
196
0
    {
197
0
        return stPermissions.bDataCanWrite;  // FIXME: Check on resource level
198
                                             // is this permission set?
199
0
    }
200
0
    else if (EQUAL(pszCap, ODsCRandomLayerRead))
201
0
    {
202
0
        return stPermissions.bDataCanRead;
203
0
    }
204
0
    else if (EQUAL(pszCap, ODsCZGeometries))
205
0
    {
206
0
        return TRUE;
207
0
    }
208
0
    else if (EQUAL(pszCap, ODsCAddFieldDomain))
209
0
    {
210
0
        return stPermissions.bResourceCanCreate;
211
0
    }
212
0
    else if (EQUAL(pszCap, ODsCDeleteFieldDomain))
213
0
    {
214
0
        return stPermissions.bResourceCanDelete;
215
0
    }
216
0
    else if (EQUAL(pszCap, ODsCUpdateFieldDomain))
217
0
    {
218
0
        return stPermissions.bResourceCanUpdate;
219
0
    }
220
0
    else
221
0
    {
222
0
        return FALSE;
223
0
    }
224
0
}
225
226
/*
227
 * GetLayer()
228
 */
229
OGRLayer *OGRNGWDataset::GetLayer(int iLayer)
230
0
{
231
0
    if (iLayer < 0 || iLayer >= GetLayerCount())
232
0
    {
233
0
        return nullptr;
234
0
    }
235
0
    else
236
0
    {
237
0
        return aoLayers[iLayer].get();
238
0
    }
239
0
}
240
241
/*
242
 * Open()
243
 */
244
bool OGRNGWDataset::Open(const std::string &osUrlIn,
245
                         const std::string &osResourceIdIn,
246
                         char **papszOpenOptionsIn, bool bUpdateIn,
247
                         int nOpenFlagsIn)
248
313
{
249
313
    osUrl = osUrlIn;
250
313
    osResourceId = osResourceIdIn;
251
252
313
    eAccess = bUpdateIn ? GA_Update : GA_ReadOnly;
253
254
313
    osUserPwd = CSLFetchNameValueDef(papszOpenOptionsIn, "USERPWD",
255
313
                                     CPLGetConfigOption("NGW_USERPWD", ""));
256
257
313
    nBatchSize =
258
313
        atoi(CSLFetchNameValueDef(papszOpenOptionsIn, "BATCH_SIZE",
259
313
                                  CPLGetConfigOption("NGW_BATCH_SIZE", "-1")));
260
261
313
    nPageSize =
262
313
        atoi(CSLFetchNameValueDef(papszOpenOptionsIn, "PAGE_SIZE",
263
313
                                  CPLGetConfigOption("NGW_PAGE_SIZE", "-1")));
264
313
    if (nPageSize == 0)
265
0
    {
266
0
        nPageSize = -1;
267
0
    }
268
269
313
    nCacheExpires = atoi(CSLFetchNameValueDef(
270
313
        papszOpenOptionsIn, "CACHE_EXPIRES",
271
313
        CPLGetConfigOption("NGW_CACHE_EXPIRES", "604800")));
272
273
313
    nCacheMaxSize = atoi(CSLFetchNameValueDef(
274
313
        papszOpenOptionsIn, "CACHE_MAX_SIZE",
275
313
        CPLGetConfigOption("NGW_CACHE_MAX_SIZE", "67108864")));
276
277
313
    bExtInNativeData =
278
313
        CPLFetchBool(papszOpenOptionsIn, "NATIVE_DATA",
279
313
                     CPLTestBool(CPLGetConfigOption("NGW_NATIVE_DATA", "NO")));
280
281
313
    osJsonDepth =
282
313
        CSLFetchNameValueDef(papszOpenOptionsIn, "JSON_DEPTH",
283
313
                             CPLGetConfigOption("NGW_JSON_DEPTH", "32"));
284
285
313
    osExtensions =
286
313
        CSLFetchNameValueDef(papszOpenOptionsIn, "EXTENSIONS",
287
313
                             CPLGetConfigOption("NGW_EXTENSIONS", ""));
288
289
313
    osConnectTimeout =
290
313
        CSLFetchNameValueDef(papszOpenOptionsIn, "CONNECTTIMEOUT",
291
313
                             CPLGetConfigOption("NGW_CONNECTTIMEOUT", ""));
292
313
    osTimeout = CSLFetchNameValueDef(papszOpenOptionsIn, "TIMEOUT",
293
313
                                     CPLGetConfigOption("NGW_TIMEOUT", ""));
294
313
    osRetryCount =
295
313
        CSLFetchNameValueDef(papszOpenOptionsIn, "MAX_RETRY",
296
313
                             CPLGetConfigOption("NGW_MAX_RETRY", ""));
297
313
    osRetryDelay =
298
313
        CSLFetchNameValueDef(papszOpenOptionsIn, "RETRY_DELAY",
299
313
                             CPLGetConfigOption("NGW_RETRY_DELAY", ""));
300
301
313
    if (osExtensions.empty())
302
313
    {
303
313
        bExtInNativeData = false;
304
313
    }
305
306
313
    CPLDebug("NGW",
307
313
             "Open options:\n"
308
313
             "  BATCH_SIZE %d\n"
309
313
             "  PAGE_SIZE %d\n"
310
313
             "  CACHE_EXPIRES %d\n"
311
313
             "  CACHE_MAX_SIZE %d\n"
312
313
             "  JSON_DEPTH %s\n"
313
313
             "  EXTENSIONS %s\n"
314
313
             "  CONNECTTIMEOUT %s\n"
315
313
             "  TIMEOUT %s\n"
316
313
             "  MAX_RETRY %s\n"
317
313
             "  RETRY_DELAY %s",
318
313
             nBatchSize, nPageSize, nCacheExpires, nCacheMaxSize,
319
313
             osJsonDepth.c_str(), osExtensions.c_str(),
320
313
             osConnectTimeout.c_str(), osTimeout.c_str(), osRetryCount.c_str(),
321
313
             osRetryDelay.c_str());
322
323
313
    return Init(nOpenFlagsIn);
324
313
}
325
326
/*
327
 * Open()
328
 *
329
 * The pszFilename templates:
330
 *      - NGW:http://some.nextgis.com/resource/0
331
 *      - NGW:http://some.nextgis.com:8000/test/resource/0
332
 */
333
bool OGRNGWDataset::Open(const char *pszFilename, char **papszOpenOptionsIn,
334
                         bool bUpdateIn, int nOpenFlagsIn)
335
353
{
336
353
    NGWAPI::Uri stUri = NGWAPI::ParseUri(pszFilename);
337
338
353
    if (stUri.osPrefix != "NGW")
339
40
    {
340
40
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported name %s",
341
40
                 pszFilename);
342
40
        return false;
343
40
    }
344
345
313
    osUrl = stUri.osAddress;
346
313
    osResourceId = stUri.osResourceId;
347
348
313
    return Open(stUri.osAddress, stUri.osResourceId, papszOpenOptionsIn,
349
313
                bUpdateIn, nOpenFlagsIn);
350
353
}
351
352
/*
353
 * SetupRasterDSWrapper()
354
 */
355
void OGRNGWDataset::SetupRasterDSWrapper(const OGREnvelope &stExtent)
356
0
{
357
0
    if (poRasterDS)
358
0
    {
359
0
        nRasterXSize = poRasterDS->GetRasterXSize();
360
0
        nRasterYSize = poRasterDS->GetRasterYSize();
361
362
0
        for (int iBand = 1; iBand <= poRasterDS->GetRasterCount(); iBand++)
363
0
        {
364
0
            SetBand(iBand,
365
0
                    new NGWWrapperRasterBand(poRasterDS->GetRasterBand(iBand)));
366
0
        }
367
368
0
        if (stExtent.IsInit())
369
0
        {
370
            // Set pixel limits.
371
0
            bool bHasTransform = false;
372
0
            GDALGeoTransform gt, invGT;
373
0
            if (poRasterDS->GetGeoTransform(gt) == CE_None)
374
0
            {
375
0
                bHasTransform = gt.GetInverse(invGT);
376
0
            }
377
378
0
            if (bHasTransform)
379
0
            {
380
0
                invGT.Apply(stExtent.MinX, stExtent.MinY, &stPixelExtent.MinX,
381
0
                            &stPixelExtent.MaxY);
382
383
0
                invGT.Apply(stExtent.MaxX, stExtent.MaxY, &stPixelExtent.MaxX,
384
0
                            &stPixelExtent.MinY);
385
386
0
                CPLDebug("NGW", "Raster extent in px is: %f, %f, %f, %f",
387
0
                         stPixelExtent.MinX, stPixelExtent.MinY,
388
0
                         stPixelExtent.MaxX, stPixelExtent.MaxY);
389
0
            }
390
0
            else
391
0
            {
392
0
                stPixelExtent.MinX = 0.0;
393
0
                stPixelExtent.MinY = 0.0;
394
0
                stPixelExtent.MaxX = std::numeric_limits<double>::max();
395
0
                stPixelExtent.MaxY = std::numeric_limits<double>::max();
396
0
            }
397
0
        }
398
0
    }
399
0
}
400
401
/*
402
 * Init()
403
 */
404
bool OGRNGWDataset::Init(int nOpenFlagsIn)
405
313
{
406
    // NOTE: Skip check API version at that moment. We expected API v3 or higher.
407
408
    // Get resource details.
409
313
    CPLJSONDocument oResourceDetailsReq;
410
313
    auto aosHTTPOptions = GetHeaders(false);
411
313
    bool bResult = oResourceDetailsReq.LoadUrl(
412
313
        NGWAPI::GetResourceURL(osUrl, osResourceId), aosHTTPOptions);
413
414
313
    CPLDebug("NGW", "Get resource %s details %s", osResourceId.c_str(),
415
313
             bResult ? "success" : "failed");
416
417
313
    if (bResult)
418
0
    {
419
0
        CPLJSONObject oRoot = oResourceDetailsReq.GetRoot();
420
421
0
        if (oRoot.IsValid())
422
0
        {
423
0
            auto osResourceType = oRoot.GetString("resource/cls");
424
0
            FillMetadata(oRoot);
425
426
0
            if (osResourceType == "resource_group")
427
0
            {
428
                // Check feature paging.
429
0
                FillCapabilities(aosHTTPOptions);
430
0
                if (oRoot.GetBool("resource/children", false))
431
0
                {
432
                    // Get child resources.
433
0
                    bResult = FillResources(aosHTTPOptions, nOpenFlagsIn);
434
0
                }
435
0
            }
436
0
            else if (NGWAPI::CheckSupportedType(false, osResourceType))
437
0
            {
438
                // Check feature paging.
439
0
                FillCapabilities(aosHTTPOptions);
440
                // Add vector layer.
441
0
                AddLayer(oRoot, aosHTTPOptions, nOpenFlagsIn);
442
0
            }
443
0
            else if (osResourceType == "mapserver_style" ||
444
0
                     osResourceType == "qgis_vector_style" ||
445
0
                     osResourceType == "raster_style" ||
446
0
                     osResourceType == "qgis_raster_style")
447
0
            {
448
                // GetExtent from parent.
449
0
                OGREnvelope stExtent;
450
0
                std::string osParentId = oRoot.GetString("resource/parent/id");
451
0
                bool bExtentResult = NGWAPI::GetExtent(
452
0
                    osUrl, osParentId, aosHTTPOptions, 3857, stExtent);
453
454
0
                if (!bExtentResult)
455
0
                {
456
                    // Set full extent for EPSG:3857.
457
0
                    stExtent.MinX = -20037508.34;
458
0
                    stExtent.MaxX = 20037508.34;
459
0
                    stExtent.MinY = -20037508.34;
460
0
                    stExtent.MaxY = 20037508.34;
461
0
                }
462
463
0
                CPLDebug("NGW", "Raster extent is: %f, %f, %f, %f",
464
0
                         stExtent.MinX, stExtent.MinY, stExtent.MaxX,
465
0
                         stExtent.MaxY);
466
467
0
                int nEPSG = 3857;
468
                // NOTE: Get parent details. We can skip this as default SRS in
469
                // NGW is 3857.
470
0
                CPLJSONDocument oResourceReq;
471
0
                bResult = oResourceReq.LoadUrl(
472
0
                    NGWAPI::GetResourceURL(osUrl, osResourceId),
473
0
                    aosHTTPOptions);
474
475
0
                if (bResult)
476
0
                {
477
0
                    CPLJSONObject oParentRoot = oResourceReq.GetRoot();
478
0
                    if (osResourceType == "mapserver_style" ||
479
0
                        osResourceType == "qgis_vector_style")
480
0
                    {
481
0
                        nEPSG = oParentRoot.GetInteger("vector_layer/srs/id",
482
0
                                                       nEPSG);
483
0
                    }
484
0
                    else if (osResourceType == "raster_style" ||
485
0
                             osResourceType == "qgis_raster_style")
486
0
                    {
487
0
                        nEPSG = oParentRoot.GetInteger("raster_layer/srs/id",
488
0
                                                       nEPSG);
489
0
                    }
490
0
                }
491
492
0
                const char *pszConnStr = FormGDALTMSConnectionString(
493
0
                    osUrl, osResourceId, nEPSG, nCacheExpires, nCacheMaxSize);
494
0
                CPLDebug("NGW", "Open %s as '%s'", osResourceType.c_str(),
495
0
                         pszConnStr);
496
0
                poRasterDS = GDALDataset::FromHandle(GDALOpenEx(
497
0
                    pszConnStr,
498
0
                    GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_INTERNAL,
499
0
                    nullptr, nullptr, nullptr));
500
0
                SetupRasterDSWrapper(stExtent);
501
0
            }
502
0
            else if (osResourceType == "wmsclient_layer")
503
0
            {
504
0
                OGREnvelope stExtent;
505
                // Set full extent for EPSG:3857.
506
0
                stExtent.MinX = -20037508.34;
507
0
                stExtent.MaxX = 20037508.34;
508
0
                stExtent.MinY = -20037508.34;
509
0
                stExtent.MaxY = 20037508.34;
510
511
0
                CPLDebug("NGW", "Raster extent is: %f, %f, %f, %f",
512
0
                         stExtent.MinX, stExtent.MinY, stExtent.MaxX,
513
0
                         stExtent.MaxY);
514
515
0
                int nEPSG = oRoot.GetInteger("wmsclient_layer/srs/id", 3857);
516
517
0
                const char *pszConnStr = FormGDALTMSConnectionString(
518
0
                    osUrl, osResourceId, nEPSG, nCacheExpires, nCacheMaxSize);
519
0
                poRasterDS = GDALDataset::FromHandle(GDALOpenEx(
520
0
                    pszConnStr,
521
0
                    GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_INTERNAL,
522
0
                    nullptr, nullptr, nullptr));
523
0
                SetupRasterDSWrapper(stExtent);
524
0
            }
525
0
            else if (osResourceType == "basemap_layer")
526
0
            {
527
0
                auto osTMSURL = oRoot.GetString("basemap_layer/url");
528
0
                int nEPSG = 3857;
529
0
                auto osQMS = oRoot.GetString("basemap_layer/qms");
530
0
                if (!osQMS.empty())
531
0
                {
532
0
                    CPLJSONDocument oDoc;
533
0
                    if (oDoc.LoadMemory(osQMS))
534
0
                    {
535
0
                        auto oQMLRoot = oDoc.GetRoot();
536
0
                        nEPSG = oQMLRoot.GetInteger("epsg");
537
0
                    }
538
0
                }
539
540
                // TODO: for EPSG != 3857 need to calc full extent
541
0
                if (nEPSG != 3857)
542
0
                {
543
0
                    bResult = false;
544
0
                }
545
0
                else
546
0
                {
547
0
                    OGREnvelope stExtent;
548
                    // Set full extent for EPSG:3857.
549
0
                    stExtent.MinX = -20037508.34;
550
0
                    stExtent.MaxX = 20037508.34;
551
0
                    stExtent.MinY = -20037508.34;
552
0
                    stExtent.MaxY = 20037508.34;
553
554
0
                    const char *pszConnStr = FormGDALTMSConnectionString(
555
0
                        osTMSURL, osResourceId, nEPSG, nCacheExpires,
556
0
                        nCacheMaxSize);
557
0
                    poRasterDS = GDALDataset::FromHandle(GDALOpenEx(
558
0
                        pszConnStr,
559
0
                        GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_INTERNAL,
560
0
                        nullptr, nullptr, nullptr));
561
0
                    SetupRasterDSWrapper(stExtent);
562
0
                }
563
0
            }
564
0
            else if (osResourceType == "webmap")
565
0
            {
566
0
                OGREnvelope stExtent;
567
                // Set full extent for EPSG:3857.
568
0
                stExtent.MinX = -20037508.34;
569
0
                stExtent.MaxX = 20037508.34;
570
0
                stExtent.MinY = -20037508.34;
571
0
                stExtent.MaxY = 20037508.34;
572
573
                // Get all styles
574
0
                auto aoChildren = oRoot.GetArray("webmap/children");
575
0
                auto sIdentifiers = GetStylesIdentifiers(aoChildren, 0);
576
577
0
                const char *pszConnStr = FormGDALTMSConnectionString(
578
0
                    osUrl, sIdentifiers, 3857, nCacheExpires, nCacheMaxSize);
579
0
                poRasterDS = GDALDataset::FromHandle(GDALOpenEx(
580
0
                    pszConnStr,
581
0
                    GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_INTERNAL,
582
0
                    nullptr, nullptr, nullptr));
583
0
                SetupRasterDSWrapper(stExtent);
584
0
            }
585
0
            else if (osResourceType == "raster_layer")
586
0
            {
587
0
                auto osCogURL = NGWAPI::GetCOGURL(osUrl, osResourceId);
588
0
                auto osConnStr = std::string("/vsicurl/") + osCogURL;
589
590
0
                CPLDebug("NGW", "Raster url is: %s", osConnStr.c_str());
591
592
0
                poRasterDS = GDALDataset::FromHandle(GDALOpenEx(
593
0
                    osConnStr.c_str(),
594
0
                    GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_INTERNAL,
595
0
                    nullptr, nullptr, nullptr));
596
597
                // Add styles if exists
598
0
                auto osRasterResourceId = oRoot.GetString("resource/id");
599
0
                CPLJSONDocument oResourceRequest;
600
0
                bool bLoadResult = oResourceRequest.LoadUrl(
601
0
                    NGWAPI::GetChildrenURL(osUrl, osRasterResourceId),
602
0
                    aosHTTPOptions);
603
0
                if (bLoadResult)
604
0
                {
605
0
                    CPLJSONArray oChildren(oResourceRequest.GetRoot());
606
0
                    for (const auto &oChild : oChildren)
607
0
                    {
608
0
                        AddRaster(oChild);
609
0
                    }
610
0
                }
611
612
0
                SetupRasterDSWrapper(OGREnvelope());
613
0
            }
614
0
            else
615
0
            {
616
0
                bResult = false;
617
0
            }
618
619
            // TODO: Add support for wfsserver_service, wmsserver_service,
620
            // raster_mosaic, tileset.
621
0
        }
622
0
    }
623
624
313
    return bResult;
625
313
}
626
627
/*
628
 * FillResources()
629
 */
630
bool OGRNGWDataset::FillResources(const CPLStringList &aosHTTPOptions,
631
                                  int nOpenFlagsIn)
632
0
{
633
0
    CPLJSONDocument oResourceDetailsReq;
634
    // Fill domains
635
0
    bool bResult = oResourceDetailsReq.LoadUrl(
636
0
        NGWAPI::GetSearchURL(osUrl, "cls", "lookup_table"), aosHTTPOptions);
637
0
    if (bResult)
638
0
    {
639
0
        CPLJSONArray oChildren(oResourceDetailsReq.GetRoot());
640
0
        for (const auto &oChild : oChildren)
641
0
        {
642
0
            OGRNGWCodedFieldDomain oDomain(oChild);
643
0
            if (oDomain.GetID() > 0)
644
0
            {
645
0
                moDomains[oDomain.GetID()] = std::move(oDomain);
646
0
            }
647
0
        }
648
0
    }
649
650
    // Fill child resources
651
0
    bResult = oResourceDetailsReq.LoadUrl(
652
0
        NGWAPI::GetChildrenURL(osUrl, osResourceId), aosHTTPOptions);
653
654
0
    if (bResult)
655
0
    {
656
0
        CPLJSONArray oChildren(oResourceDetailsReq.GetRoot());
657
0
        for (const auto &oChild : oChildren)
658
0
        {
659
0
            if (nOpenFlagsIn & GDAL_OF_VECTOR)
660
0
            {
661
                // Add vector layer. If failed, try next layer.
662
0
                AddLayer(oChild, aosHTTPOptions, nOpenFlagsIn);
663
0
            }
664
665
0
            if (nOpenFlagsIn & GDAL_OF_RASTER)
666
0
            {
667
0
                AddRaster(oChild);
668
0
            }
669
0
        }
670
0
    }
671
0
    return bResult;
672
0
}
673
674
/*
675
 * AddLayer()
676
 */
677
void OGRNGWDataset::AddLayer(const CPLJSONObject &oResourceJsonObject,
678
                             const CPLStringList &aosHTTPOptions,
679
                             int nOpenFlagsIn)
680
0
{
681
0
    auto osResourceType = oResourceJsonObject.GetString("resource/cls");
682
0
    if (!NGWAPI::CheckSupportedType(false, osResourceType))
683
0
    {
684
        // NOTE: Only vector_layer and postgis_layer types now supported
685
0
        return;
686
0
    }
687
688
0
    auto osLayerResourceId = oResourceJsonObject.GetString("resource/id");
689
0
    if (nOpenFlagsIn & GDAL_OF_VECTOR)
690
0
    {
691
0
        OGRNGWLayerPtr poLayer(new OGRNGWLayer(this, oResourceJsonObject));
692
0
        aoLayers.emplace_back(poLayer);
693
0
        osLayerResourceId = poLayer->GetResourceId();
694
0
    }
695
696
    // Check styles exist and add them as rasters.
697
0
    if (nOpenFlagsIn & GDAL_OF_RASTER &&
698
0
        oResourceJsonObject.GetBool("resource/children", false))
699
0
    {
700
0
        CPLJSONDocument oResourceChildReq;
701
0
        bool bResult = oResourceChildReq.LoadUrl(
702
0
            NGWAPI::GetChildrenURL(osUrl, osLayerResourceId), aosHTTPOptions);
703
704
0
        if (bResult)
705
0
        {
706
0
            CPLJSONArray oChildren(oResourceChildReq.GetRoot());
707
0
            for (const auto &oChild : oChildren)
708
0
            {
709
0
                AddRaster(oChild);
710
0
            }
711
0
        }
712
0
    }
713
0
}
714
715
/*
716
 * AddRaster()
717
 */
718
void OGRNGWDataset::AddRaster(const CPLJSONObject &oRasterJsonObj)
719
0
{
720
0
    auto osResourceType = oRasterJsonObj.GetString("resource/cls");
721
0
    if (!NGWAPI::CheckSupportedType(true, osResourceType))
722
0
    {
723
0
        return;
724
0
    }
725
726
0
    auto osOutResourceId = oRasterJsonObj.GetString("resource/id");
727
0
    auto osOutResourceName = oRasterJsonObj.GetString("resource/display_name");
728
729
0
    if (osOutResourceName.empty())
730
0
    {
731
0
        osOutResourceName = "raster_" + osOutResourceId;
732
0
    }
733
734
0
    CPLDebug("NGW", "Add raster %s: %s", osOutResourceId.c_str(),
735
0
             osOutResourceName.c_str());
736
737
0
    GDALDataset::SetMetadataItem(CPLSPrintf("SUBDATASET_%d_NAME", nRasters + 1),
738
0
                                 CPLSPrintf("NGW:%s/resource/%s", osUrl.c_str(),
739
0
                                            osOutResourceId.c_str()),
740
0
                                 "SUBDATASETS");
741
0
    GDALDataset::SetMetadataItem(CPLSPrintf("SUBDATASET_%d_DESC", nRasters + 1),
742
0
                                 CPLSPrintf("%s (%s)",
743
0
                                            osOutResourceName.c_str(),
744
0
                                            osResourceType.c_str()),
745
0
                                 "SUBDATASETS");
746
0
    nRasters++;
747
0
}
748
749
/*
750
 * ICreateLayer
751
 */
752
OGRLayer *OGRNGWDataset::ICreateLayer(const char *pszNameIn,
753
                                      const OGRGeomFieldDefn *poGeomFieldDefn,
754
                                      CSLConstList papszOptions)
755
0
{
756
0
    if (!IsUpdateMode())
757
0
    {
758
0
        CPLError(CE_Failure, CPLE_AppDefined,
759
0
                 "Operation not available in read-only mode");
760
0
        return nullptr;
761
0
    }
762
763
0
    const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
764
0
    const auto poSpatialRef =
765
0
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
766
767
    // Check permissions as we create new layer in memory and will create in
768
    // during SyncToDisk.
769
0
    FetchPermissions();
770
771
0
    if (!stPermissions.bResourceCanCreate)
772
0
    {
773
0
        CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
774
0
        return nullptr;
775
0
    }
776
777
    // Check input parameters.
778
0
    if ((eGType < wkbPoint || eGType > wkbMultiPolygon) &&
779
0
        (eGType < wkbPoint25D || eGType > wkbMultiPolygon25D))
780
0
    {
781
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unsupported geometry type: %s",
782
0
                 OGRGeometryTypeToName(eGType));
783
0
        return nullptr;
784
0
    }
785
786
0
    if (!poSpatialRef)
787
0
    {
788
0
        CPLError(CE_Failure, CPLE_AppDefined, "Undefined spatial reference");
789
0
        return nullptr;
790
0
    }
791
792
0
    OGRSpatialReference *poSRSClone = poSpatialRef->Clone();
793
0
    poSRSClone->AutoIdentifyEPSG();
794
0
    const char *pszEPSG = poSRSClone->GetAuthorityCode(nullptr);
795
0
    int nEPSG = -1;
796
0
    if (pszEPSG != nullptr)
797
0
    {
798
0
        nEPSG = atoi(pszEPSG);
799
0
    }
800
801
0
    if (nEPSG != 3857)  // TODO: Check NextGIS Web supported SRS.
802
0
    {
803
0
        CPLError(CE_Failure, CPLE_AppDefined,
804
0
                 "Unsupported spatial reference EPSG code: %d", nEPSG);
805
0
        poSRSClone->Release();
806
0
        return nullptr;
807
0
    }
808
809
    // Do we already have this layer?  If so, should we blow it away?
810
0
    bool bOverwrite = CPLFetchBool(papszOptions, "OVERWRITE", false);
811
0
    for (int iLayer = 0; iLayer < GetLayerCount(); ++iLayer)
812
0
    {
813
0
        if (EQUAL(pszNameIn, aoLayers[iLayer]->GetName()))
814
0
        {
815
0
            if (bOverwrite)
816
0
            {
817
0
                DeleteLayer(iLayer);
818
0
                break;
819
0
            }
820
0
            else
821
0
            {
822
0
                CPLError(CE_Failure, CPLE_AppDefined,
823
0
                         "Layer %s already exists, CreateLayer failed.\n"
824
0
                         "Use the layer creation option OVERWRITE=YES to "
825
0
                         "replace it.",
826
0
                         pszNameIn);
827
0
                poSRSClone->Release();
828
0
                return nullptr;
829
0
            }
830
0
        }
831
0
    }
832
833
    // Create layer.
834
0
    std::string osKey = CSLFetchNameValueDef(papszOptions, "KEY", "");
835
0
    std::string osDesc = CSLFetchNameValueDef(papszOptions, "DESCRIPTION", "");
836
0
    poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
837
0
    OGRNGWLayerPtr poLayer(
838
0
        new OGRNGWLayer(this, pszNameIn, poSRSClone, eGType, osKey, osDesc));
839
0
    poSRSClone->Release();
840
0
    aoLayers.emplace_back(poLayer);
841
0
    return poLayer.get();
842
0
}
843
844
/*
845
 * DeleteLayer()
846
 */
847
OGRErr OGRNGWDataset::DeleteLayer(int iLayer)
848
0
{
849
0
    if (!IsUpdateMode())
850
0
    {
851
0
        CPLError(CE_Failure, CPLE_AppDefined,
852
0
                 "Operation not available in read-only mode.");
853
0
        return OGRERR_FAILURE;
854
0
    }
855
856
0
    if (iLayer < 0 || iLayer >= GetLayerCount())
857
0
    {
858
0
        CPLError(CE_Failure, CPLE_AppDefined,
859
0
                 "Layer %d not in legal range of 0 to %d.", iLayer,
860
0
                 GetLayerCount() - 1);
861
0
        return OGRERR_FAILURE;
862
0
    }
863
864
0
    auto poLayer = aoLayers[iLayer];
865
0
    if (poLayer->GetResourceId() != "-1")
866
0
    {
867
        // For layers from server we can check permissions.
868
869
        // We can skip check permissions here as papoLayers[iLayer]->Delete()
870
        // will return false if no delete permission available.
871
0
        FetchPermissions();
872
873
0
        if (!stPermissions.bResourceCanDelete)
874
0
        {
875
0
            CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
876
0
            return OGRERR_FAILURE;
877
0
        }
878
0
    }
879
880
0
    if (poLayer->Delete())
881
0
    {
882
0
        aoLayers.erase(aoLayers.begin() + iLayer);
883
0
    }
884
885
0
    return OGRERR_NONE;
886
0
}
887
888
/*
889
 * FillMetadata()
890
 */
891
void OGRNGWDataset::FillMetadata(const CPLJSONObject &oRootObject)
892
0
{
893
0
    std::string osCreateDate = oRootObject.GetString("resource/creation_date");
894
0
    if (!osCreateDate.empty())
895
0
    {
896
0
        GDALDataset::SetMetadataItem("creation_date", osCreateDate.c_str());
897
0
    }
898
0
    osName = oRootObject.GetString("resource/display_name");
899
0
    SetDescription(osName.c_str());
900
0
    GDALDataset::SetMetadataItem("display_name", osName.c_str());
901
0
    std::string osDescription = oRootObject.GetString("resource/description");
902
0
    if (!osDescription.empty())
903
0
    {
904
0
        GDALDataset::SetMetadataItem("description", osDescription.c_str());
905
0
    }
906
0
    std::string osResourceType = oRootObject.GetString("resource/cls");
907
0
    if (!osResourceType.empty())
908
0
    {
909
0
        GDALDataset::SetMetadataItem("resource_type", osResourceType.c_str());
910
0
    }
911
0
    std::string osResourceParentId =
912
0
        oRootObject.GetString("resource/parent/id");
913
0
    if (!osResourceParentId.empty())
914
0
    {
915
0
        GDALDataset::SetMetadataItem("parent_id", osResourceParentId.c_str());
916
0
    }
917
0
    GDALDataset::SetMetadataItem("id", osResourceId.c_str());
918
919
0
    std::vector<CPLJSONObject> items =
920
0
        oRootObject.GetObj("resmeta/items").GetChildren();
921
922
0
    for (const CPLJSONObject &item : items)
923
0
    {
924
0
        std::string osSuffix = NGWAPI::GetResmetaSuffix(item.GetType());
925
0
        GDALDataset::SetMetadataItem((item.GetName() + osSuffix).c_str(),
926
0
                                     item.ToString().c_str(), "NGW");
927
0
    }
928
0
}
929
930
/*
931
 * FlushMetadata()
932
 */
933
bool OGRNGWDataset::FlushMetadata(char **papszMetadata)
934
353
{
935
353
    if (!bMetadataDerty)
936
353
    {
937
353
        return true;
938
353
    }
939
940
0
    bool bResult = NGWAPI::FlushMetadata(osUrl, osResourceId, papszMetadata,
941
0
                                         GetHeaders(false));
942
0
    if (bResult)
943
0
    {
944
0
        bMetadataDerty = false;
945
0
    }
946
947
0
    return bResult;
948
353
}
949
950
/*
951
 * SetMetadata()
952
 */
953
CPLErr OGRNGWDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
954
0
{
955
0
    FetchPermissions();
956
0
    if (!stPermissions.bMetadataCanWrite)
957
0
    {
958
0
        CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
959
0
        return CE_Failure;
960
0
    }
961
962
0
    CPLErr eResult = GDALDataset::SetMetadata(papszMetadata, pszDomain);
963
0
    if (eResult == CE_None && pszDomain != nullptr && EQUAL(pszDomain, "NGW"))
964
0
    {
965
0
        eResult = FlushMetadata(papszMetadata) ? CE_None : CE_Failure;
966
0
    }
967
0
    return eResult;
968
0
}
969
970
/*
971
 * SetMetadataItem()
972
 */
973
CPLErr OGRNGWDataset::SetMetadataItem(const char *pszName, const char *pszValue,
974
                                      const char *pszDomain)
975
0
{
976
0
    FetchPermissions();
977
0
    if (!stPermissions.bMetadataCanWrite)
978
0
    {
979
0
        CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
980
0
        return CE_Failure;
981
0
    }
982
0
    if (pszDomain != nullptr && EQUAL(pszDomain, "NGW"))
983
0
    {
984
0
        bMetadataDerty = true;
985
0
    }
986
0
    return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
987
0
}
988
989
/*
990
 * FlushCache()
991
 */
992
CPLErr OGRNGWDataset::FlushCache(bool bAtClosing)
993
353
{
994
353
    CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
995
353
    if (!FlushMetadata(GetMetadata("NGW")))
996
0
        eErr = CE_Failure;
997
353
    return eErr;
998
353
}
999
1000
/*
1001
 * GetHeaders()
1002
 */
1003
CPLStringList OGRNGWDataset::GetHeaders(bool bSkipRetry) const
1004
313
{
1005
313
    CPLStringList aosOptions;
1006
313
    aosOptions.AddNameValue("HEADERS", "Accept: */*");
1007
313
    aosOptions.AddNameValue("JSON_DEPTH", osJsonDepth.c_str());
1008
313
    if (!osUserPwd.empty())
1009
0
    {
1010
0
        aosOptions.AddNameValue("HTTPAUTH", "BASIC");
1011
0
        aosOptions.AddNameValue("USERPWD", osUserPwd.c_str());
1012
0
    }
1013
1014
313
    if (!osConnectTimeout.empty())
1015
0
    {
1016
0
        aosOptions.AddNameValue("CONNECTTIMEOUT", osConnectTimeout.c_str());
1017
0
    }
1018
1019
313
    if (!osTimeout.empty())
1020
0
    {
1021
0
        aosOptions.AddNameValue("TIMEOUT", osTimeout.c_str());
1022
0
    }
1023
1024
313
    if (!bSkipRetry)
1025
313
    {
1026
313
        if (!osRetryCount.empty())
1027
0
        {
1028
0
            aosOptions.AddNameValue("MAX_RETRY", osRetryCount.c_str());
1029
0
        }
1030
313
        if (!osRetryDelay.empty())
1031
0
        {
1032
0
            aosOptions.AddNameValue("RETRY_DELAY", osRetryDelay.c_str());
1033
0
        }
1034
313
    }
1035
313
    return aosOptions;
1036
313
}
1037
1038
/*
1039
 * SQLUnescape()
1040
 * Get from gdal/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp as we don't want
1041
 * dependency on sqlite
1042
 */
1043
static CPLString SQLUnescape(const char *pszVal)
1044
0
{
1045
0
    char chQuoteChar = pszVal[0];
1046
0
    if (chQuoteChar != '\'' && chQuoteChar != '"')
1047
0
        return pszVal;
1048
1049
0
    CPLString osRet;
1050
0
    pszVal++;
1051
0
    while (*pszVal != '\0')
1052
0
    {
1053
0
        if (*pszVal == chQuoteChar)
1054
0
        {
1055
0
            if (pszVal[1] == chQuoteChar)
1056
0
                pszVal++;
1057
0
            else
1058
0
                break;
1059
0
        }
1060
0
        osRet += *pszVal;
1061
0
        pszVal++;
1062
0
    }
1063
0
    return osRet;
1064
0
}
1065
1066
/*
1067
 * SQLTokenize()
1068
 * Get from gdal/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp as we don't want
1069
 * dependency on sqlite
1070
 */
1071
static char **SQLTokenize(const char *pszStr)
1072
0
{
1073
0
    char **papszTokens = nullptr;
1074
0
    bool bInQuote = false;
1075
0
    char chQuoteChar = '\0';
1076
0
    bool bInSpace = true;
1077
0
    CPLString osCurrentToken;
1078
0
    while (*pszStr != '\0')
1079
0
    {
1080
0
        if (*pszStr == ' ' && !bInQuote)
1081
0
        {
1082
0
            if (!bInSpace)
1083
0
            {
1084
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
1085
0
                osCurrentToken.clear();
1086
0
            }
1087
0
            bInSpace = true;
1088
0
        }
1089
0
        else if ((*pszStr == '(' || *pszStr == ')' || *pszStr == ',') &&
1090
0
                 !bInQuote)
1091
0
        {
1092
0
            if (!bInSpace)
1093
0
            {
1094
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
1095
0
                osCurrentToken.clear();
1096
0
            }
1097
0
            osCurrentToken.clear();
1098
0
            osCurrentToken += *pszStr;
1099
0
            papszTokens = CSLAddString(papszTokens, osCurrentToken);
1100
0
            osCurrentToken.clear();
1101
0
            bInSpace = true;
1102
0
        }
1103
0
        else if (*pszStr == '"' || *pszStr == '\'')
1104
0
        {
1105
0
            if (bInQuote && *pszStr == chQuoteChar && pszStr[1] == chQuoteChar)
1106
0
            {
1107
0
                osCurrentToken += *pszStr;
1108
0
                osCurrentToken += *pszStr;
1109
0
                pszStr += 2;
1110
0
                continue;
1111
0
            }
1112
0
            else if (bInQuote && *pszStr == chQuoteChar)
1113
0
            {
1114
0
                osCurrentToken += *pszStr;
1115
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
1116
0
                osCurrentToken.clear();
1117
0
                bInSpace = true;
1118
0
                bInQuote = false;
1119
0
                chQuoteChar = '\0';
1120
0
            }
1121
0
            else if (bInQuote)
1122
0
            {
1123
0
                osCurrentToken += *pszStr;
1124
0
            }
1125
0
            else
1126
0
            {
1127
0
                chQuoteChar = *pszStr;
1128
0
                osCurrentToken.clear();
1129
0
                osCurrentToken += chQuoteChar;
1130
0
                bInQuote = true;
1131
0
                bInSpace = false;
1132
0
            }
1133
0
        }
1134
0
        else
1135
0
        {
1136
0
            osCurrentToken += *pszStr;
1137
0
            bInSpace = false;
1138
0
        }
1139
0
        pszStr++;
1140
0
    }
1141
1142
0
    if (!osCurrentToken.empty())
1143
0
        papszTokens = CSLAddString(papszTokens, osCurrentToken);
1144
1145
0
    return papszTokens;
1146
0
}
1147
1148
/*
1149
 * ExecuteSQL()
1150
 */
1151
OGRLayer *OGRNGWDataset::ExecuteSQL(const char *pszStatement,
1152
                                    OGRGeometry *poSpatialFilter,
1153
                                    const char *pszDialect)
1154
0
{
1155
    // Clean statement string.
1156
0
    CPLString osStatement(pszStatement);
1157
0
    osStatement = osStatement.Trim().replaceAll("  ", " ");
1158
1159
0
    if (STARTS_WITH_CI(osStatement, "DELLAYER:"))
1160
0
    {
1161
0
        CPLString osLayerName = osStatement.substr(strlen("DELLAYER:"));
1162
0
        if (osLayerName.endsWith(";"))
1163
0
        {
1164
0
            osLayerName = osLayerName.substr(0, osLayerName.size() - 1);
1165
0
            osLayerName.Trim();
1166
0
        }
1167
1168
0
        CPLDebug("NGW", "Delete layer with name %s.", osLayerName.c_str());
1169
1170
0
        for (int iLayer = 0; iLayer < GetLayerCount(); ++iLayer)
1171
0
        {
1172
0
            if (EQUAL(aoLayers[iLayer]->GetName(), osLayerName))
1173
0
            {
1174
0
                DeleteLayer(iLayer);
1175
0
                return nullptr;
1176
0
            }
1177
0
        }
1178
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
1179
0
                 osLayerName.c_str());
1180
1181
0
        return nullptr;
1182
0
    }
1183
1184
0
    if (STARTS_WITH_CI(osStatement, "DELETE FROM"))
1185
0
    {
1186
0
        osStatement = osStatement.substr(strlen("DELETE FROM "));
1187
0
        if (osStatement.endsWith(";"))
1188
0
        {
1189
0
            osStatement = osStatement.substr(0, osStatement.size() - 1);
1190
0
            osStatement.Trim();
1191
0
        }
1192
1193
0
        std::size_t found = osStatement.find("WHERE");
1194
0
        CPLString osLayerName;
1195
0
        if (found == std::string::npos)
1196
0
        {  // No where clause
1197
0
            osLayerName = osStatement;
1198
0
            osStatement.clear();
1199
0
        }
1200
0
        else
1201
0
        {
1202
0
            osLayerName = osStatement.substr(0, found);
1203
0
            osLayerName.Trim();
1204
0
            osStatement = osStatement.substr(found + strlen("WHERE "));
1205
0
        }
1206
1207
0
        OGRNGWLayer *poLayer =
1208
0
            reinterpret_cast<OGRNGWLayer *>(GetLayerByName(osLayerName));
1209
0
        if (nullptr == poLayer)
1210
0
        {
1211
0
            CPLError(CE_Failure, CPLE_AppDefined,
1212
0
                     "Layer %s not found in dataset.", osName.c_str());
1213
0
            return nullptr;
1214
0
        }
1215
1216
0
        if (osStatement.empty())
1217
0
        {
1218
0
            poLayer->DeleteAllFeatures();
1219
0
        }
1220
0
        else
1221
0
        {
1222
0
            CPLDebug("NGW", "Delete features with statement %s",
1223
0
                     osStatement.c_str());
1224
0
            OGRFeatureQuery oQuery;
1225
0
            OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), osStatement);
1226
0
            if (eErr != OGRERR_NONE)
1227
0
            {
1228
0
                return nullptr;
1229
0
            }
1230
1231
            // Ignore all fields except first and ignore geometry
1232
0
            auto poLayerDefn = poLayer->GetLayerDefn();
1233
0
            poLayerDefn->SetGeometryIgnored(TRUE);
1234
0
            if (poLayerDefn->GetFieldCount() > 0)
1235
0
            {
1236
0
                std::set<std::string> osFields;
1237
0
                OGRFieldDefn *poFieldDefn = poLayerDefn->GetFieldDefn(0);
1238
0
                osFields.insert(poFieldDefn->GetNameRef());
1239
0
                poLayer->SetSelectedFields(osFields);
1240
0
            }
1241
0
            CPLString osNgwDelete =
1242
0
                "NGW:" +
1243
0
                OGRNGWLayer::TranslateSQLToFilter(
1244
0
                    reinterpret_cast<swq_expr_node *>(oQuery.GetSWQExpr()));
1245
1246
0
            poLayer->SetAttributeFilter(osNgwDelete);
1247
1248
0
            std::vector<GIntBig> aiFeaturesIDs;
1249
0
            OGRFeature *poFeat;
1250
0
            while ((poFeat = poLayer->GetNextFeature()) != nullptr)
1251
0
            {
1252
0
                aiFeaturesIDs.push_back(poFeat->GetFID());
1253
0
                OGRFeature::DestroyFeature(poFeat);
1254
0
            }
1255
1256
0
            poLayer->DeleteFeatures(aiFeaturesIDs);
1257
1258
            // Reset all filters and ignores
1259
0
            poLayerDefn->SetGeometryIgnored(FALSE);
1260
0
            poLayer->SetAttributeFilter(nullptr);
1261
0
            poLayer->SetIgnoredFields(nullptr);
1262
0
        }
1263
0
        return nullptr;
1264
0
    }
1265
1266
0
    if (STARTS_WITH_CI(osStatement, "DROP TABLE"))
1267
0
    {
1268
        // Get layer name from pszStatement DELETE FROM layer;.
1269
0
        CPLString osLayerName = osStatement.substr(strlen("DROP TABLE "));
1270
0
        if (osLayerName.endsWith(";"))
1271
0
        {
1272
0
            osLayerName = osLayerName.substr(0, osLayerName.size() - 1);
1273
0
            osLayerName.Trim();
1274
0
        }
1275
1276
0
        CPLDebug("NGW", "Delete layer with name %s.", osLayerName.c_str());
1277
1278
0
        for (int iLayer = 0; iLayer < GetLayerCount(); ++iLayer)
1279
0
        {
1280
0
            if (EQUAL(aoLayers[iLayer]->GetName(), osLayerName))
1281
0
            {
1282
0
                DeleteLayer(iLayer);
1283
0
                return nullptr;
1284
0
            }
1285
0
        }
1286
1287
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
1288
0
                 osLayerName.c_str());
1289
1290
0
        return nullptr;
1291
0
    }
1292
1293
0
    if (STARTS_WITH_CI(osStatement, "ALTER TABLE "))
1294
0
    {
1295
0
        if (osStatement.endsWith(";"))
1296
0
        {
1297
0
            osStatement = osStatement.substr(0, osStatement.size() - 1);
1298
0
            osStatement.Trim();
1299
0
        }
1300
1301
0
        CPLStringList aosTokens(SQLTokenize(osStatement));
1302
        /* ALTER TABLE src_table RENAME TO dst_table */
1303
0
        if (aosTokens.size() == 6 && EQUAL(aosTokens[3], "RENAME") &&
1304
0
            EQUAL(aosTokens[4], "TO"))
1305
0
        {
1306
0
            const char *pszSrcTableName = aosTokens[2];
1307
0
            const char *pszDstTableName = aosTokens[5];
1308
1309
0
            OGRNGWLayer *poLayer = static_cast<OGRNGWLayer *>(
1310
0
                GetLayerByName(SQLUnescape(pszSrcTableName)));
1311
0
            if (poLayer)
1312
0
            {
1313
0
                poLayer->Rename(SQLUnescape(pszDstTableName));
1314
0
                return nullptr;
1315
0
            }
1316
1317
0
            CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
1318
0
                     pszSrcTableName);
1319
0
        }
1320
0
        else
1321
0
        {
1322
0
            CPLError(CE_Failure, CPLE_AppDefined,
1323
0
                     "Unsupported alter table operation. Only rename table to "
1324
0
                     "... support.");
1325
0
        }
1326
0
        return nullptr;
1327
0
    }
1328
1329
    // SELECT xxxxx FROM yyyy WHERE zzzzzz;
1330
0
    if (STARTS_WITH_CI(osStatement, "SELECT "))
1331
0
    {
1332
0
        swq_select oSelect;
1333
0
        CPLDebug("NGW", "Select statement: %s", osStatement.c_str());
1334
0
        if (oSelect.preparse(osStatement) != CE_None)
1335
0
        {
1336
0
            return nullptr;
1337
0
        }
1338
1339
0
        if (oSelect.join_count == 0 && oSelect.poOtherSelect == nullptr &&
1340
0
            oSelect.table_count == 1 && oSelect.order_specs == 0)
1341
0
        {
1342
0
            OGRNGWLayer *poLayer = reinterpret_cast<OGRNGWLayer *>(
1343
0
                GetLayerByName(oSelect.table_defs[0].table_name));
1344
0
            if (nullptr == poLayer)
1345
0
            {
1346
0
                CPLError(CE_Failure, CPLE_AppDefined,
1347
0
                         "Layer %s not found in dataset.",
1348
0
                         oSelect.table_defs[0].table_name);
1349
0
                return nullptr;
1350
0
            }
1351
1352
0
            std::set<std::string> aosFields;
1353
0
            bool bSkip = false;
1354
0
            for (int i = 0; i < oSelect.result_columns(); ++i)
1355
0
            {
1356
0
                swq_col_func col_func = oSelect.column_defs[i].col_func;
1357
0
                if (col_func != SWQCF_NONE)
1358
0
                {
1359
0
                    bSkip = true;
1360
0
                    break;
1361
0
                }
1362
1363
0
                if (oSelect.column_defs[i].distinct_flag)
1364
0
                {
1365
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1366
0
                             "Distinct not supported.");
1367
0
                    bSkip = true;
1368
0
                    break;
1369
0
                }
1370
1371
0
                if (oSelect.column_defs[i].field_name != nullptr)
1372
0
                {
1373
0
                    if (EQUAL(oSelect.column_defs[i].field_name, "*"))
1374
0
                    {
1375
0
                        aosFields.clear();
1376
0
                        aosFields.emplace(oSelect.column_defs[i].field_name);
1377
0
                        break;
1378
0
                    }
1379
0
                    else
1380
0
                    {
1381
0
                        aosFields.emplace(oSelect.column_defs[i].field_name);
1382
0
                    }
1383
0
                }
1384
0
            }
1385
1386
0
            std::string osNgwSelect;
1387
0
            for (int iKey = 0; iKey < oSelect.order_specs; iKey++)
1388
0
            {
1389
0
                swq_order_def *psKeyDef = oSelect.order_defs + iKey;
1390
0
                if (iKey > 0)
1391
0
                {
1392
0
                    osNgwSelect += ",";
1393
0
                }
1394
1395
0
                if (psKeyDef->ascending_flag == TRUE)
1396
0
                {
1397
0
                    osNgwSelect += psKeyDef->field_name;
1398
0
                }
1399
0
                else
1400
0
                {
1401
0
                    osNgwSelect += "-" + std::string(psKeyDef->field_name);
1402
0
                }
1403
0
            }
1404
1405
0
            if (oSelect.where_expr != nullptr)
1406
0
            {
1407
0
                if (!osNgwSelect.empty())
1408
0
                {
1409
0
                    osNgwSelect += "&";
1410
0
                }
1411
0
                osNgwSelect +=
1412
0
                    OGRNGWLayer::TranslateSQLToFilter(oSelect.where_expr);
1413
0
            }
1414
1415
0
            if (osNgwSelect.empty())
1416
0
            {
1417
0
                bSkip = true;
1418
0
            }
1419
1420
0
            if (!bSkip)
1421
0
            {
1422
0
                if (aosFields.empty())
1423
0
                {
1424
0
                    CPLError(
1425
0
                        CE_Failure, CPLE_AppDefined,
1426
0
                        "SELECT statement is invalid: field list is empty.");
1427
0
                    return nullptr;
1428
0
                }
1429
1430
0
                if (poLayer->SyncToDisk() != OGRERR_NONE)
1431
0
                {
1432
0
                    return nullptr;
1433
0
                }
1434
1435
0
                OGRNGWLayer *poOutLayer = poLayer->Clone();
1436
0
                if (aosFields.size() == 1 && *(aosFields.begin()) == "*")
1437
0
                {
1438
0
                    poOutLayer->SetIgnoredFields(nullptr);
1439
0
                }
1440
0
                else
1441
0
                {
1442
0
                    poOutLayer->SetSelectedFields(aosFields);
1443
0
                }
1444
0
                poOutLayer->SetSpatialFilter(poSpatialFilter);
1445
1446
0
                if (osNgwSelect
1447
0
                        .empty())  // If we here oSelect.where_expr is empty
1448
0
                {
1449
0
                    poOutLayer->SetAttributeFilter(nullptr);
1450
0
                }
1451
0
                else
1452
0
                {
1453
0
                    std::string osAttributeFilte = "NGW:" + osNgwSelect;
1454
0
                    poOutLayer->SetAttributeFilter(osAttributeFilte.c_str());
1455
0
                }
1456
0
                return poOutLayer;
1457
0
            }
1458
0
        }
1459
0
    }
1460
1461
0
    return GDALDataset::ExecuteSQL(pszStatement, poSpatialFilter, pszDialect);
1462
0
}
1463
1464
/*
1465
 * GetProjectionRef()
1466
 */
1467
const OGRSpatialReference *OGRNGWDataset::GetSpatialRef() const
1468
0
{
1469
0
    if (poRasterDS != nullptr)
1470
0
    {
1471
0
        return poRasterDS->GetSpatialRef();
1472
0
    }
1473
0
    return GDALDataset::GetSpatialRef();
1474
0
}
1475
1476
/*
1477
 * GetGeoTransform()
1478
 */
1479
CPLErr OGRNGWDataset::GetGeoTransform(GDALGeoTransform &gt) const
1480
0
{
1481
0
    if (poRasterDS != nullptr)
1482
0
    {
1483
0
        return poRasterDS->GetGeoTransform(gt);
1484
0
    }
1485
0
    return GDALDataset::GetGeoTransform(gt);
1486
0
}
1487
1488
/*
1489
 * IRasterIO()
1490
 */
1491
CPLErr OGRNGWDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1492
                                int nXSize, int nYSize, void *pData,
1493
                                int nBufXSize, int nBufYSize,
1494
                                GDALDataType eBufType, int nBandCount,
1495
                                BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1496
                                GSpacing nLineSpace, GSpacing nBandSpace,
1497
                                GDALRasterIOExtraArg *psExtraArg)
1498
0
{
1499
0
    if (poRasterDS != nullptr)
1500
0
    {
1501
0
        if (stPixelExtent.IsInit())
1502
0
        {
1503
0
            OGREnvelope stTestExtent;
1504
0
            stTestExtent.MinX = static_cast<double>(nXOff);
1505
0
            stTestExtent.MinY = static_cast<double>(nYOff);
1506
0
            stTestExtent.MaxX = static_cast<double>(nXOff + nXSize);
1507
0
            stTestExtent.MaxY = static_cast<double>(nYOff + nYSize);
1508
1509
0
            if (!stPixelExtent.Intersects(stTestExtent))
1510
0
            {
1511
0
                CPLDebug("NGW", "Raster extent in px is: %f, %f, %f, %f",
1512
0
                         stPixelExtent.MinX, stPixelExtent.MinY,
1513
0
                         stPixelExtent.MaxX, stPixelExtent.MaxY);
1514
0
                CPLDebug("NGW", "RasterIO extent is: %f, %f, %f, %f",
1515
0
                         stTestExtent.MinX, stTestExtent.MinY,
1516
0
                         stTestExtent.MaxX, stTestExtent.MaxY);
1517
1518
                // Fill buffer transparent color.
1519
0
                memset(pData, 0,
1520
0
                       static_cast<size_t>(nBufXSize) * nBufYSize * nBandCount *
1521
0
                           GDALGetDataTypeSizeBytes(eBufType));
1522
0
                return CE_None;
1523
0
            }
1524
0
        }
1525
0
    }
1526
0
    return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
1527
0
                                  nBufXSize, nBufYSize, eBufType, nBandCount,
1528
0
                                  panBandMap, nPixelSpace, nLineSpace,
1529
0
                                  nBandSpace, psExtraArg);
1530
0
}
1531
1532
/*
1533
 * FillCapabilities()
1534
 */
1535
void OGRNGWDataset::FillCapabilities(const CPLStringList &aosHTTPOptions)
1536
0
{
1537
    // Check NGW version. Paging available from 3.1
1538
0
    CPLJSONDocument oRouteReq;
1539
0
    if (oRouteReq.LoadUrl(NGWAPI::GetVersionURL(osUrl), aosHTTPOptions))
1540
0
    {
1541
0
        CPLJSONObject oRoot = oRouteReq.GetRoot();
1542
1543
0
        if (oRoot.IsValid())
1544
0
        {
1545
0
            std::string osVersion = oRoot.GetString("nextgisweb", "0.0");
1546
0
            bHasFeaturePaging = NGWAPI::CheckVersion(osVersion, 3, 1);
1547
1548
0
            CPLDebug("NGW", "Is feature paging supported: %s",
1549
0
                     bHasFeaturePaging ? "yes" : "no");
1550
0
        }
1551
0
    }
1552
0
}
1553
1554
/*
1555
 * Extensions()
1556
 */
1557
std::string OGRNGWDataset::Extensions() const
1558
0
{
1559
0
    return osExtensions;
1560
0
}
1561
1562
/*
1563
 * GetFieldDomainNames()
1564
 */
1565
std::vector<std::string> OGRNGWDataset::GetFieldDomainNames(CSLConstList) const
1566
0
{
1567
0
    std::vector<std::string> oDomainNamesList;
1568
0
    std::array<OGRFieldType, 3> aeFieldTypes{OFTString, OFTInteger,
1569
0
                                             OFTInteger64};
1570
0
    for (auto const &oDom : moDomains)
1571
0
    {
1572
0
        for (auto eFieldType : aeFieldTypes)
1573
0
        {
1574
0
            auto pOgrDom = oDom.second.ToFieldDomain(eFieldType);
1575
0
            if (pOgrDom != nullptr)
1576
0
            {
1577
0
                oDomainNamesList.emplace_back(pOgrDom->GetName());
1578
0
            }
1579
0
        }
1580
0
    }
1581
0
    return oDomainNamesList;
1582
0
}
1583
1584
/*
1585
 * GetFieldDomain()
1586
 */
1587
const OGRFieldDomain *
1588
OGRNGWDataset::GetFieldDomain(const std::string &name) const
1589
0
{
1590
0
    std::array<OGRFieldType, 3> aeFieldTypes{OFTString, OFTInteger,
1591
0
                                             OFTInteger64};
1592
0
    for (auto const &oDom : moDomains)
1593
0
    {
1594
0
        for (auto eFieldType : aeFieldTypes)
1595
0
        {
1596
0
            auto pOgrDom = oDom.second.ToFieldDomain(eFieldType);
1597
0
            if (pOgrDom != nullptr)
1598
0
            {
1599
0
                if (pOgrDom->GetName() == name)
1600
0
                {
1601
0
                    return pOgrDom;
1602
0
                }
1603
0
            }
1604
0
        }
1605
0
    }
1606
0
    return nullptr;
1607
0
}
1608
1609
/*
1610
 * DeleteFieldDomain()
1611
 */
1612
bool OGRNGWDataset::DeleteFieldDomain(const std::string &name,
1613
                                      std::string &failureReason)
1614
0
{
1615
0
    if (eAccess != GA_Update)
1616
0
    {
1617
0
        failureReason =
1618
0
            "DeleteFieldDomain() not supported on read-only dataset";
1619
0
        return false;
1620
0
    }
1621
1622
0
    std::array<OGRFieldType, 3> aeFieldTypes{OFTString, OFTInteger,
1623
0
                                             OFTInteger64};
1624
0
    for (auto const &oDom : moDomains)
1625
0
    {
1626
0
        for (auto eFieldType : aeFieldTypes)
1627
0
        {
1628
0
            auto pOgrDom = oDom.second.ToFieldDomain(eFieldType);
1629
0
            if (pOgrDom != nullptr)
1630
0
            {
1631
0
                if (pOgrDom->GetName() == name)
1632
0
                {
1633
0
                    auto nResourceID = oDom.second.GetID();
1634
1635
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1636
0
                             "Delete following domains with common "
1637
0
                             "identifier " CPL_FRMT_GIB ": %s.",
1638
0
                             nResourceID,
1639
0
                             oDom.second.GetDomainsNames().c_str());
1640
1641
0
                    auto result = NGWAPI::DeleteResource(
1642
0
                        GetUrl(), std::to_string(nResourceID),
1643
0
                        GetHeaders(false));
1644
0
                    if (!result)
1645
0
                    {
1646
0
                        failureReason = CPLGetLastErrorMsg();
1647
0
                        return result;
1648
0
                    }
1649
1650
0
                    moDomains.erase(nResourceID);
1651
1652
                    // Remove domain from fields definitions
1653
0
                    for (const auto &oLayer : aoLayers)
1654
0
                    {
1655
0
                        for (int i = 0;
1656
0
                             i < oLayer->GetLayerDefn()->GetFieldCount(); ++i)
1657
0
                        {
1658
0
                            OGRFieldDefn *poFieldDefn =
1659
0
                                oLayer->GetLayerDefn()->GetFieldDefn(i);
1660
0
                            if (oDom.second.HasDomainName(
1661
0
                                    poFieldDefn->GetDomainName()))
1662
0
                            {
1663
0
                                auto oTemporaryUnsealer(
1664
0
                                    poFieldDefn->GetTemporaryUnsealer());
1665
0
                                poFieldDefn->SetDomainName(std::string());
1666
0
                            }
1667
0
                        }
1668
0
                    }
1669
0
                    return true;
1670
0
                }
1671
0
            }
1672
0
        }
1673
0
    }
1674
0
    failureReason = "Domain does not exist";
1675
0
    return false;
1676
0
}
1677
1678
/*
1679
 * CreateNGWLookupTableJson()
1680
 */
1681
static std::string CreateNGWLookupTableJson(const OGRCodedFieldDomain *pDomain,
1682
                                            GIntBig nResourceId)
1683
0
{
1684
0
    CPLJSONObject oResourceJson;
1685
    // Add resource json item.
1686
0
    CPLJSONObject oResource("resource", oResourceJson);
1687
0
    oResource.Add("cls", "lookup_table");
1688
0
    CPLJSONObject oResourceParent("parent", oResource);
1689
0
    oResourceParent.Add("id", nResourceId);
1690
0
    oResource.Add("display_name", pDomain->GetName());
1691
0
    oResource.Add("description", pDomain->GetDescription());
1692
1693
    // Add vector_layer json item.
1694
0
    CPLJSONObject oLookupTable("lookup_table", oResourceJson);
1695
0
    CPLJSONObject oLookupTableItems("items", oLookupTable);
1696
0
    const auto enumeration = pDomain->GetEnumeration();
1697
0
    for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
1698
0
    {
1699
0
        const char *pszValCurrent = "";
1700
        // NGW not supported null as coded value, so set it as ""
1701
0
        if (enumeration[i].pszValue != nullptr)
1702
0
        {
1703
0
            pszValCurrent = enumeration[i].pszValue;
1704
0
        }
1705
0
        oLookupTableItems.Add(enumeration[i].pszCode, pszValCurrent);
1706
0
    }
1707
1708
0
    return oResourceJson.Format(CPLJSONObject::PrettyFormat::Plain);
1709
0
}
1710
1711
/*
1712
 * AddFieldDomain()
1713
 */
1714
bool OGRNGWDataset::AddFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
1715
                                   std::string &failureReason)
1716
0
{
1717
0
    const std::string domainName(domain->GetName());
1718
0
    if (eAccess != GA_Update)
1719
0
    {
1720
0
        failureReason = "Add field domain not supported on read-only dataset";
1721
0
        return false;
1722
0
    }
1723
1724
0
    if (GetFieldDomain(domainName) != nullptr)
1725
0
    {
1726
0
        failureReason = "A domain of identical name already exists";
1727
0
        return false;
1728
0
    }
1729
1730
0
    if (domain->GetDomainType() != OFDT_CODED)
1731
0
    {
1732
0
        failureReason = "Unsupported domain type";
1733
0
        return false;
1734
0
    }
1735
1736
0
    auto osPalyload = CreateNGWLookupTableJson(
1737
0
        static_cast<OGRCodedFieldDomain *>(domain.get()),
1738
0
        static_cast<GIntBig>(std::stol(osResourceId)));
1739
1740
0
    std::string osResourceIdInt =
1741
0
        NGWAPI::CreateResource(osUrl, osPalyload, GetHeaders());
1742
0
    if (osResourceIdInt == "-1")
1743
0
    {
1744
0
        failureReason = CPLGetLastErrorMsg();
1745
0
        return false;
1746
0
    }
1747
0
    auto osNewResourceUrl = NGWAPI::GetResourceURL(osUrl, osResourceIdInt);
1748
0
    CPLJSONDocument oResourceDetailsReq;
1749
0
    bool bResult =
1750
0
        oResourceDetailsReq.LoadUrl(osNewResourceUrl, GetHeaders(false));
1751
0
    if (!bResult)
1752
0
    {
1753
0
        failureReason = CPLGetLastErrorMsg();
1754
0
        return false;
1755
0
    }
1756
1757
0
    OGRNGWCodedFieldDomain oDomain(oResourceDetailsReq.GetRoot());
1758
0
    if (oDomain.GetID() == 0)
1759
0
    {
1760
0
        failureReason = "Failed to parse domain detailes from NGW";
1761
0
        return false;
1762
0
    }
1763
0
    moDomains[oDomain.GetID()] = std::move(oDomain);
1764
0
    return true;
1765
0
}
1766
1767
/*
1768
 * UpdateFieldDomain()
1769
 */
1770
bool OGRNGWDataset::UpdateFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
1771
                                      std::string &failureReason)
1772
0
{
1773
0
    const std::string domainName(domain->GetName());
1774
0
    if (eAccess != GA_Update)
1775
0
    {
1776
0
        failureReason = "Add field domain not supported on read-only dataset";
1777
0
        return false;
1778
0
    }
1779
1780
0
    if (GetFieldDomain(domainName) == nullptr)
1781
0
    {
1782
0
        failureReason = "The domain should already exist to be updated";
1783
0
        return false;
1784
0
    }
1785
1786
0
    if (domain->GetDomainType() != OFDT_CODED)
1787
0
    {
1788
0
        failureReason = "Unsupported domain type";
1789
0
        return false;
1790
0
    }
1791
1792
0
    auto nResourceId = GetDomainIdByName(domainName);
1793
0
    if (nResourceId == 0)
1794
0
    {
1795
0
        failureReason = "Failed get NGW domain identifier";
1796
0
        return false;
1797
0
    }
1798
1799
0
    auto osPayload = CreateNGWLookupTableJson(
1800
0
        static_cast<const OGRCodedFieldDomain *>(domain.get()),
1801
0
        static_cast<GIntBig>(std::stol(osResourceId)));
1802
1803
0
    if (!NGWAPI::UpdateResource(osUrl, osResourceId, osPayload, GetHeaders()))
1804
0
    {
1805
0
        failureReason = CPLGetLastErrorMsg();
1806
0
        return false;
1807
0
    }
1808
1809
0
    auto osNewResourceUrl = NGWAPI::GetResourceURL(osUrl, osResourceId);
1810
0
    CPLJSONDocument oResourceDetailsReq;
1811
0
    bool bResult =
1812
0
        oResourceDetailsReq.LoadUrl(osNewResourceUrl, GetHeaders(false));
1813
0
    if (!bResult)
1814
0
    {
1815
0
        failureReason = CPLGetLastErrorMsg();
1816
0
        return false;
1817
0
    }
1818
1819
0
    OGRNGWCodedFieldDomain oDomain(oResourceDetailsReq.GetRoot());
1820
0
    if (oDomain.GetID() == 0)
1821
0
    {
1822
0
        failureReason = "Failed to parse domain detailes from NGW";
1823
0
        return false;
1824
0
    }
1825
0
    moDomains[oDomain.GetID()] = std::move(oDomain);
1826
0
    return true;
1827
0
}
1828
1829
/*
1830
 * GetDomainByID()
1831
 */
1832
OGRNGWCodedFieldDomain OGRNGWDataset::GetDomainByID(GIntBig id) const
1833
0
{
1834
0
    auto pos = moDomains.find(id);
1835
0
    if (pos == moDomains.end())
1836
0
    {
1837
0
        return OGRNGWCodedFieldDomain();
1838
0
    }
1839
0
    else
1840
0
    {
1841
0
        return pos->second;
1842
0
    }
1843
0
}
1844
1845
/*
1846
 *  GetDomainIdByName()
1847
 */
1848
GIntBig OGRNGWDataset::GetDomainIdByName(const std::string &osDomainName) const
1849
0
{
1850
0
    for (auto const &oDom : moDomains)
1851
0
    {
1852
0
        if (oDom.second.HasDomainName(osDomainName))
1853
0
        {
1854
0
            return oDom.first;
1855
0
        }
1856
0
    }
1857
0
    return 0L;
1858
0
}