LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/ngw - gdalngwdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 0 806 0.0 %
Date: 2025-07-13 09:04:35 Functions: 0 41 0.0 %

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

Generated by: LCOV version 1.14