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 : }
|