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

Generated by: LCOV version 1.14