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