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