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
10 : *
11 : * SPDX-License-Identifier: MIT
12 : *******************************************************************************/
13 :
14 : #include "ogr_ngw.h"
15 :
16 : #include "cpl_http.h"
17 :
18 : #include <limits>
19 :
20 : namespace NGWAPI
21 : {
22 :
23 0 : static std::string GetErrorMessage(const CPLJSONObject &oRoot,
24 : const std::string &osErrorMessage)
25 : {
26 0 : if (oRoot.IsValid())
27 : {
28 : std::string osErrorMessageInt =
29 0 : oRoot.GetString("message", osErrorMessage);
30 0 : if (!osErrorMessageInt.empty())
31 : {
32 0 : return osErrorMessageInt;
33 : }
34 : }
35 0 : return osErrorMessage;
36 : }
37 :
38 0 : bool CheckRequestResult(bool bResult, const CPLJSONObject &oRoot,
39 : const std::string &osErrorMessage)
40 : {
41 0 : if (!bResult)
42 : {
43 0 : auto osMsg = GetErrorMessage(oRoot, osErrorMessage);
44 :
45 0 : CPLError(CE_Failure, CPLE_AppDefined,
46 : "NGW driver failed to fetch data with error: %s",
47 : osMsg.c_str());
48 0 : return false;
49 : }
50 :
51 0 : return true;
52 : }
53 :
54 0 : static void ReportError(const GByte *pabyData, int nDataLen,
55 : const std::string &osErrorMessage)
56 : {
57 0 : CPLJSONDocument oResult;
58 0 : if (oResult.LoadMemory(pabyData, nDataLen))
59 : {
60 0 : CPLJSONObject oRoot = oResult.GetRoot();
61 0 : auto osMsg = GetErrorMessage(oRoot, osErrorMessage);
62 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osMsg.c_str());
63 : }
64 : else
65 : {
66 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMessage.c_str());
67 : }
68 0 : }
69 :
70 0 : bool CheckSupportedType(bool bIsRaster, const std::string &osType)
71 : {
72 : //TODO: Add "raster_mosaic", "tileset", "wfsserver_service" and "wmsserver_service"
73 0 : if (bIsRaster)
74 : {
75 0 : if (osType == "mapserver_style" || osType == "qgis_vector_style" ||
76 0 : osType == "raster_style" || osType == "qgis_raster_style" ||
77 0 : osType == "basemap_layer" || osType == "webmap" ||
78 0 : osType == "wmsclient_layer" || osType == "raster_layer")
79 : {
80 0 : return true;
81 : }
82 : }
83 : else
84 : {
85 0 : if (osType == "vector_layer" || osType == "postgis_layer")
86 : {
87 0 : return true;
88 : }
89 : }
90 0 : return false;
91 : }
92 :
93 0 : std::string GetPermissionsURL(const std::string &osUrl,
94 : const std::string &osResourceId)
95 : {
96 0 : return osUrl + "/api/resource/" + osResourceId + "/permission";
97 : }
98 :
99 0 : std::string GetResourceURL(const std::string &osUrl,
100 : const std::string &osResourceId)
101 : {
102 0 : return osUrl + "/api/resource/" + osResourceId;
103 : }
104 :
105 0 : std::string GetChildrenURL(const std::string &osUrl,
106 : const std::string &osResourceId)
107 : {
108 0 : return osUrl + "/api/resource/?parent=" + osResourceId;
109 : }
110 :
111 0 : std::string GetFeatureURL(const std::string &osUrl,
112 : const std::string &osResourceId)
113 : {
114 0 : return osUrl + "/api/resource/" + osResourceId + "/feature/";
115 : }
116 :
117 0 : std::string GetTMSURL(const std::string &osUrl, const std::string &osResourceId)
118 : {
119 0 : return osUrl +
120 : "/api/component/render/"
121 : "tile?z=${z}&x=${x}&y=${y}&resource=" +
122 0 : osResourceId;
123 : }
124 :
125 0 : std::string GetSearchURL(const std::string &osUrl, const std::string &osKey,
126 : const std::string &osValue)
127 : {
128 0 : return osUrl + "/api/resource/search/?" + osKey + "=" + osValue;
129 : }
130 :
131 : std::string
132 0 : GetFeaturePageURL(const std::string &osUrl, const std::string &osResourceId,
133 : GIntBig nStart, int nCount, const std::string &osFields,
134 : const std::string &osWhere, const std::string &osSpatialWhere,
135 : const std::string &osExtensions, bool IsGeometryIgnored)
136 : {
137 0 : std::string osFeatureUrl = GetFeatureURL(osUrl, osResourceId);
138 0 : bool bParamAdd = false;
139 0 : if (nCount > 0)
140 : {
141 0 : osFeatureUrl += "?offset=" + std::to_string(nStart) +
142 0 : "&limit=" + std::to_string(nCount);
143 0 : bParamAdd = true;
144 : }
145 :
146 0 : if (!osFields.empty())
147 : {
148 0 : if (bParamAdd)
149 : {
150 0 : osFeatureUrl += "&fields=" + osFields;
151 : }
152 : else
153 : {
154 0 : osFeatureUrl += "?fields=" + osFields;
155 0 : bParamAdd = true;
156 : }
157 : }
158 :
159 0 : if (!osWhere.empty())
160 : {
161 0 : if (bParamAdd)
162 : {
163 0 : osFeatureUrl += "&" + osWhere;
164 : }
165 : else
166 : {
167 0 : osFeatureUrl += "?" + osWhere;
168 0 : bParamAdd = true;
169 : }
170 : }
171 :
172 0 : if (!osSpatialWhere.empty())
173 : {
174 0 : if (bParamAdd)
175 : {
176 0 : osFeatureUrl += "&intersects=" + osSpatialWhere;
177 : }
178 : else
179 : {
180 0 : osFeatureUrl += "?intersects=" + osSpatialWhere;
181 0 : bParamAdd = true;
182 : }
183 : }
184 :
185 0 : if (IsGeometryIgnored)
186 : {
187 0 : if (bParamAdd)
188 : {
189 0 : osFeatureUrl += "&geom=no";
190 : }
191 : else
192 : {
193 0 : osFeatureUrl += "?geom=no";
194 0 : bParamAdd = true;
195 : }
196 : }
197 :
198 0 : if (bParamAdd)
199 : {
200 0 : osFeatureUrl += "&extensions=" + osExtensions;
201 : }
202 : else
203 : {
204 0 : osFeatureUrl += "?extensions=" + osExtensions;
205 : }
206 :
207 0 : return osFeatureUrl;
208 : }
209 :
210 0 : std::string GetRouteURL(const std::string &osUrl)
211 : {
212 0 : return osUrl + "/api/component/pyramid/route";
213 : }
214 :
215 0 : std::string GetUploadURL(const std::string &osUrl)
216 : {
217 0 : return osUrl + "/api/component/file_upload/upload";
218 : }
219 :
220 0 : std::string GetVersionURL(const std::string &osUrl)
221 : {
222 0 : return osUrl + "/api/component/pyramid/pkg_version";
223 : }
224 :
225 0 : std::string GetCOGURL(const std::string &osUrl, const std::string &osResourceId)
226 : {
227 0 : return osUrl + "/api/resource/" + osResourceId + "/cog";
228 : }
229 :
230 0 : bool CheckVersion(const std::string &osVersion, int nMajor, int nMinor,
231 : int nPatch)
232 : {
233 0 : int nCurrentMajor(0);
234 0 : int nCurrentMinor(0);
235 0 : int nCurrentPatch(0);
236 :
237 0 : CPLStringList aosList(CSLTokenizeString2(osVersion.c_str(), ".", 0));
238 0 : if (aosList.size() > 2)
239 : {
240 0 : nCurrentMajor = atoi(aosList[0]);
241 0 : nCurrentMinor = atoi(aosList[1]);
242 0 : nCurrentPatch = atoi(aosList[2]);
243 : }
244 0 : else if (aosList.size() > 1)
245 : {
246 0 : nCurrentMajor = atoi(aosList[0]);
247 0 : nCurrentMinor = atoi(aosList[1]);
248 : }
249 0 : else if (aosList.size() > 0)
250 : {
251 0 : nCurrentMajor = atoi(aosList[0]);
252 : }
253 :
254 0 : int nCheckVersion = nMajor * 1000 + nMinor * 100 + nPatch;
255 0 : int nCurrentVersion =
256 0 : nCurrentMajor * 1000 + nCurrentMinor * 100 + nCurrentPatch;
257 0 : return nCurrentVersion >= nCheckVersion;
258 : }
259 :
260 50 : Uri ParseUri(const std::string &osUrl)
261 : {
262 50 : Uri stOut;
263 50 : std::size_t nFound = osUrl.find(":");
264 50 : if (nFound == std::string::npos)
265 : {
266 50 : return stOut;
267 : }
268 :
269 0 : stOut.osPrefix = osUrl.substr(0, nFound);
270 0 : std::string osUrlInt = CPLString(osUrl.substr(nFound + 1)).tolower();
271 :
272 0 : nFound = osUrlInt.find("/resource/");
273 0 : if (nFound == std::string::npos)
274 : {
275 0 : return stOut;
276 : }
277 :
278 0 : stOut.osAddress = osUrlInt.substr(0, nFound);
279 :
280 : std::string osResourceId =
281 0 : CPLString(osUrlInt.substr(nFound + strlen("/resource/"))).Trim();
282 :
283 0 : nFound = osResourceId.find('/');
284 0 : if (nFound != std::string::npos)
285 : {
286 0 : stOut.osResourceId = osResourceId.substr(0, nFound);
287 0 : stOut.osNewResourceName = osResourceId.substr(nFound + 1);
288 : }
289 : else
290 : {
291 0 : stOut.osResourceId = std::move(osResourceId);
292 : }
293 :
294 0 : return stOut;
295 : }
296 :
297 0 : std::string CreateResource(const std::string &osUrl,
298 : const std::string &osPayload,
299 : const CPLStringList &aosHTTPOptions)
300 : {
301 0 : CPLErrorReset();
302 0 : std::string osPayloadInt = "POSTFIELDS=" + osPayload;
303 :
304 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
305 :
306 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=POST");
307 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
308 : aosHTTPOptionsInt.AddString(
309 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
310 :
311 0 : CPLDebug("NGW", "CreateResource request payload: %s", osPayload.c_str());
312 :
313 0 : CPLJSONDocument oCreateReq;
314 : bool bResult =
315 0 : oCreateReq.LoadUrl(GetResourceURL(osUrl, ""), aosHTTPOptionsInt);
316 0 : std::string osResourceId("-1");
317 0 : CPLJSONObject oRoot = oCreateReq.GetRoot();
318 0 : if (CheckRequestResult(bResult, oRoot, "CreateResource request failed"))
319 : {
320 0 : osResourceId = oRoot.GetString("id", "-1");
321 : }
322 0 : return osResourceId;
323 : }
324 :
325 0 : bool UpdateResource(const std::string &osUrl, const std::string &osResourceId,
326 : const std::string &osPayload,
327 : const CPLStringList &aosHTTPOptions)
328 : {
329 0 : CPLErrorReset();
330 0 : std::string osPayloadInt = "POSTFIELDS=" + osPayload;
331 :
332 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
333 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=PUT");
334 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
335 : aosHTTPOptionsInt.AddString(
336 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
337 :
338 0 : CPLDebug("NGW", "UpdateResource request payload: %s", osPayload.c_str());
339 :
340 0 : CPLHTTPResult *psResult = CPLHTTPFetch(
341 0 : GetResourceURL(osUrl, osResourceId).c_str(), aosHTTPOptionsInt);
342 0 : bool bResult = false;
343 0 : if (psResult)
344 : {
345 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
346 :
347 : // Get error message.
348 0 : if (!bResult)
349 : {
350 0 : ReportError(psResult->pabyData, psResult->nDataLen,
351 : "UpdateResource request failed");
352 : }
353 0 : CPLHTTPDestroyResult(psResult);
354 : }
355 : else
356 : {
357 0 : CPLError(CE_Failure, CPLE_AppDefined, "Update resource %s failed",
358 : osResourceId.c_str());
359 : }
360 0 : return bResult;
361 : }
362 :
363 0 : bool DeleteResource(const std::string &osUrl, const std::string &osResourceId,
364 : const CPLStringList &aosHTTPOptions)
365 : {
366 0 : CPLErrorReset();
367 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
368 :
369 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=DELETE");
370 0 : auto osUrlNew = GetResourceURL(osUrl, osResourceId);
371 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlNew.c_str(), aosHTTPOptionsInt);
372 0 : bool bResult = false;
373 0 : if (psResult)
374 : {
375 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
376 : // Get error message.
377 0 : if (!bResult)
378 : {
379 0 : ReportError(psResult->pabyData, psResult->nDataLen,
380 : "DeleteResource request failed");
381 : }
382 0 : CPLHTTPDestroyResult(psResult);
383 : }
384 0 : return bResult;
385 : }
386 :
387 0 : bool RenameResource(const std::string &osUrl, const std::string &osResourceId,
388 : const std::string &osNewName,
389 : const CPLStringList &aosHTTPOptions)
390 : {
391 0 : CPLJSONObject oPayload;
392 0 : CPLJSONObject oResource("resource", oPayload);
393 0 : oResource.Add("display_name", osNewName);
394 0 : std::string osPayload = oPayload.Format(CPLJSONObject::PrettyFormat::Plain);
395 :
396 0 : return UpdateResource(osUrl, osResourceId, osPayload, aosHTTPOptions);
397 : }
398 :
399 0 : OGRwkbGeometryType NGWGeomTypeToOGRGeomType(const std::string &osGeomType)
400 : {
401 : // http://docs.nextgis.com/docs_ngweb_dev/doc/developer/vector_data_types.html#nextgisweb.feature_layer.interface.GEOM_TYPE
402 0 : if (osGeomType == "POINT")
403 0 : return wkbPoint;
404 0 : else if (osGeomType == "LINESTRING")
405 0 : return wkbLineString;
406 0 : else if (osGeomType == "POLYGON")
407 0 : return wkbPolygon;
408 0 : else if (osGeomType == "MULTIPOINT")
409 0 : return wkbMultiPoint;
410 0 : else if (osGeomType == "MULTILINESTRING")
411 0 : return wkbMultiLineString;
412 0 : else if (osGeomType == "MULTIPOLYGON")
413 0 : return wkbMultiPolygon;
414 0 : else if (osGeomType == "POINTZ")
415 0 : return wkbPoint25D;
416 0 : else if (osGeomType == "LINESTRINGZ")
417 0 : return wkbLineString25D;
418 0 : else if (osGeomType == "POLYGONZ")
419 0 : return wkbPolygon25D;
420 0 : else if (osGeomType == "MULTIPOINTZ")
421 0 : return wkbMultiPoint25D;
422 0 : else if (osGeomType == "MULTILINESTRINGZ")
423 0 : return wkbMultiLineString25D;
424 0 : else if (osGeomType == "MULTIPOLYGONZ")
425 0 : return wkbMultiPolygon25D;
426 : else
427 0 : return wkbUnknown;
428 : }
429 :
430 0 : std::string OGRGeomTypeToNGWGeomType(OGRwkbGeometryType eType)
431 : {
432 0 : switch (eType)
433 : { // Don't flatten
434 0 : case wkbPoint:
435 0 : return "POINT";
436 0 : case wkbLineString:
437 0 : return "LINESTRING";
438 0 : case wkbPolygon:
439 0 : return "POLYGON";
440 0 : case wkbMultiPoint:
441 0 : return "MULTIPOINT";
442 0 : case wkbMultiLineString:
443 0 : return "MULTILINESTRING";
444 0 : case wkbMultiPolygon:
445 0 : return "MULTIPOLYGON";
446 0 : case wkbPoint25D:
447 0 : return "POINTZ";
448 0 : case wkbLineString25D:
449 0 : return "LINESTRINGZ";
450 0 : case wkbPolygon25D:
451 0 : return "POLYGONZ";
452 0 : case wkbMultiPoint25D:
453 0 : return "MULTIPOINTZ";
454 0 : case wkbMultiLineString25D:
455 0 : return "MULTILINESTRINGZ";
456 0 : case wkbMultiPolygon25D:
457 0 : return "MULTIPOLYGONZ";
458 0 : default:
459 0 : return "";
460 : }
461 : }
462 :
463 0 : OGRFieldType NGWFieldTypeToOGRFieldType(const std::string &osFieldType)
464 : {
465 : // http://docs.nextgis.com/docs_ngweb_dev/doc/developer/vector_data_types.html#nextgisweb.feature_layer.interface.FIELD_TYPE
466 0 : if (osFieldType == "INTEGER")
467 0 : return OFTInteger;
468 0 : else if (osFieldType == "BIGINT")
469 0 : return OFTInteger64;
470 0 : else if (osFieldType == "REAL")
471 0 : return OFTReal;
472 0 : else if (osFieldType == "STRING")
473 0 : return OFTString;
474 0 : else if (osFieldType == "DATE")
475 0 : return OFTDate;
476 0 : else if (osFieldType == "TIME")
477 0 : return OFTTime;
478 0 : else if (osFieldType == "DATETIME")
479 0 : return OFTDateTime;
480 : else
481 0 : return OFTString;
482 : }
483 :
484 0 : std::string OGRFieldTypeToNGWFieldType(OGRFieldType eType)
485 : {
486 0 : switch (eType)
487 : {
488 0 : case OFTInteger:
489 0 : return "INTEGER";
490 0 : case OFTInteger64:
491 0 : return "BIGINT";
492 0 : case OFTReal:
493 0 : return "REAL";
494 0 : case OFTString:
495 0 : return "STRING";
496 0 : case OFTDate:
497 0 : return "DATE";
498 0 : case OFTTime:
499 0 : return "TIME";
500 0 : case OFTDateTime:
501 0 : return "DATETIME";
502 0 : default:
503 0 : return "STRING";
504 : }
505 : }
506 :
507 0 : Permissions CheckPermissions(const std::string &osUrl,
508 : const std::string &osResourceId,
509 : const CPLStringList &aosHTTPOptions,
510 : bool bReadWrite)
511 : {
512 0 : Permissions stOut;
513 0 : CPLErrorReset();
514 0 : CPLJSONDocument oPermissionReq;
515 :
516 0 : auto osUrlNew = GetPermissionsURL(osUrl, osResourceId);
517 0 : bool bResult = oPermissionReq.LoadUrl(osUrlNew, aosHTTPOptions);
518 :
519 0 : CPLJSONObject oRoot = oPermissionReq.GetRoot();
520 0 : if (CheckRequestResult(bResult, oRoot, "Get permissions failed"))
521 : {
522 0 : stOut.bResourceCanRead = oRoot.GetBool("resource/read", true);
523 0 : stOut.bResourceCanCreate = oRoot.GetBool("resource/create", bReadWrite);
524 0 : stOut.bResourceCanUpdate = oRoot.GetBool("resource/update", bReadWrite);
525 0 : stOut.bResourceCanDelete = oRoot.GetBool("resource/delete", bReadWrite);
526 :
527 0 : stOut.bDatastructCanRead = oRoot.GetBool("datastruct/read", true);
528 0 : stOut.bDatastructCanWrite =
529 0 : oRoot.GetBool("datastruct/write", bReadWrite);
530 :
531 0 : stOut.bDataCanRead = oRoot.GetBool("data/read", true);
532 0 : stOut.bDataCanWrite = oRoot.GetBool("data/write", bReadWrite);
533 :
534 0 : stOut.bMetadataCanRead = oRoot.GetBool("metadata/read", true);
535 0 : stOut.bMetadataCanWrite = oRoot.GetBool("metadata/write", bReadWrite);
536 :
537 0 : CPLErrorReset(); // If we are here no error occurred
538 0 : return stOut;
539 : }
540 :
541 0 : return stOut;
542 : }
543 :
544 0 : std::string GetFeatureCount(const std::string &osUrl,
545 : const std::string &osResourceId)
546 : {
547 0 : return osUrl + "/api/resource/" + osResourceId + "/feature_count";
548 : }
549 :
550 0 : std::string GetLayerExtent(const std::string &osUrl,
551 : const std::string &osResourceId)
552 : {
553 0 : return osUrl + "/api/resource/" + osResourceId + "/extent";
554 : }
555 :
556 0 : std::string GetResmetaSuffix(CPLJSONObject::Type eType)
557 : {
558 0 : switch (eType)
559 : {
560 0 : case CPLJSONObject::Type::Integer:
561 : case CPLJSONObject::Type::Long:
562 0 : return ".d";
563 0 : case CPLJSONObject::Type::Double:
564 0 : return ".f";
565 0 : default:
566 0 : return "";
567 : }
568 : }
569 :
570 0 : void FillResmeta(const CPLJSONObject &oRoot, char **papszMetadata)
571 : {
572 0 : CPLJSONObject oResMeta("resmeta", oRoot);
573 0 : CPLJSONObject oResMetaItems("items", oResMeta);
574 0 : CPLStringList oaMetadata(papszMetadata, FALSE);
575 0 : for (int i = 0; i < oaMetadata.size(); ++i)
576 : {
577 0 : std::string osItem = oaMetadata[i];
578 0 : size_t nPos = osItem.find("=");
579 0 : if (nPos != std::string::npos)
580 : {
581 0 : std::string osItemName = osItem.substr(0, nPos);
582 0 : CPLString osItemValue = osItem.substr(nPos + 1);
583 :
584 0 : if (osItemName.size() > 2)
585 : {
586 0 : size_t nSuffixPos = osItemName.size() - 2;
587 0 : std::string osSuffix = osItemName.substr(nSuffixPos);
588 0 : if (osSuffix == ".d")
589 : {
590 0 : GInt64 nVal = CPLAtoGIntBig(osItemValue.c_str());
591 0 : oResMetaItems.Add(osItemName.substr(0, nSuffixPos), nVal);
592 0 : continue;
593 : }
594 :
595 0 : if (osSuffix == ".f")
596 : {
597 0 : oResMetaItems.Add(osItemName.substr(0, nSuffixPos),
598 : CPLAtofM(osItemValue.c_str()));
599 0 : continue;
600 : }
601 : }
602 :
603 0 : oResMetaItems.Add(osItemName, osItemValue);
604 : }
605 : }
606 0 : }
607 :
608 0 : bool FlushMetadata(const std::string &osUrl, const std::string &osResourceId,
609 : char **papszMetadata, const CPLStringList &aosHTTPOptions)
610 : {
611 0 : if (nullptr == papszMetadata)
612 : {
613 0 : return true;
614 : }
615 0 : CPLJSONObject oMetadataJson;
616 0 : FillResmeta(oMetadataJson, papszMetadata);
617 :
618 0 : return UpdateResource(
619 : osUrl, osResourceId,
620 0 : oMetadataJson.Format(CPLJSONObject::PrettyFormat::Plain),
621 0 : aosHTTPOptions);
622 : }
623 :
624 0 : bool DeleteFeature(const std::string &osUrl, const std::string &osResourceId,
625 : const std::string &osFeatureId,
626 : const CPLStringList &aosHTTPOptions)
627 : {
628 0 : CPLErrorReset();
629 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
630 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=DELETE");
631 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId) + osFeatureId;
632 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
633 0 : bool bResult = false;
634 0 : if (psResult)
635 : {
636 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
637 : // Get error message.
638 0 : if (!bResult)
639 : {
640 0 : ReportError(psResult->pabyData, psResult->nDataLen,
641 : "DeleteFeature request failed");
642 : }
643 0 : CPLHTTPDestroyResult(psResult);
644 : }
645 0 : return bResult;
646 : }
647 :
648 0 : bool DeleteFeatures(const std::string &osUrl, const std::string &osResourceId,
649 : const std::string &osFeaturesIDJson,
650 : const CPLStringList &aosHTTPOptions)
651 : {
652 0 : CPLErrorReset();
653 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeaturesIDJson;
654 :
655 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
656 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=DELETE");
657 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
658 : aosHTTPOptionsInt.AddString(
659 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
660 :
661 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
662 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
663 0 : bool bResult = false;
664 0 : if (psResult)
665 : {
666 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
667 : // Get error message.
668 0 : if (!bResult)
669 : {
670 0 : ReportError(psResult->pabyData, psResult->nDataLen,
671 : "DeleteFeatures request failed");
672 : }
673 0 : CPLHTTPDestroyResult(psResult);
674 : }
675 0 : return bResult;
676 : }
677 :
678 0 : GIntBig CreateFeature(const std::string &osUrl, const std::string &osResourceId,
679 : const std::string &osFeatureJson,
680 : const CPLStringList &aosHTTPOptions)
681 : {
682 0 : CPLErrorReset();
683 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeatureJson;
684 :
685 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
686 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=POST");
687 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
688 : aosHTTPOptionsInt.AddString(
689 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
690 :
691 0 : CPLDebug("NGW", "CreateFeature request payload: %s", osFeatureJson.c_str());
692 :
693 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
694 :
695 0 : CPLJSONDocument oCreateFeatureReq;
696 0 : bool bResult = oCreateFeatureReq.LoadUrl(osUrlInt, aosHTTPOptionsInt);
697 :
698 0 : CPLJSONObject oRoot = oCreateFeatureReq.GetRoot();
699 0 : GIntBig nOutFID = OGRNullFID;
700 0 : if (CheckRequestResult(bResult, oRoot, "Create new feature failed"))
701 : {
702 0 : nOutFID = oRoot.GetLong("id", OGRNullFID);
703 : }
704 :
705 0 : CPLDebug("NGW", "CreateFeature new FID: " CPL_FRMT_GIB, nOutFID);
706 0 : return nOutFID;
707 : }
708 :
709 0 : bool UpdateFeature(const std::string &osUrl, const std::string &osResourceId,
710 : const std::string &osFeatureId,
711 : const std::string &osFeatureJson,
712 : const CPLStringList &aosHTTPOptions)
713 : {
714 0 : CPLErrorReset();
715 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeatureJson;
716 :
717 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
718 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=PUT");
719 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
720 : aosHTTPOptionsInt.AddString(
721 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
722 :
723 0 : CPLDebug("NGW", "UpdateFeature request payload: %s", osFeatureJson.c_str());
724 :
725 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId) + osFeatureId;
726 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
727 0 : bool bResult = false;
728 0 : if (psResult)
729 : {
730 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
731 :
732 : // Get error message.
733 0 : if (!bResult)
734 : {
735 0 : ReportError(psResult->pabyData, psResult->nDataLen,
736 : "UpdateFeature request failed");
737 : }
738 0 : CPLHTTPDestroyResult(psResult);
739 : }
740 0 : return bResult;
741 : }
742 :
743 0 : std::vector<GIntBig> PatchFeatures(const std::string &osUrl,
744 : const std::string &osResourceId,
745 : const std::string &osFeaturesJson,
746 : const CPLStringList &aosHTTPOptions)
747 : {
748 0 : std::vector<GIntBig> aoFIDs;
749 0 : CPLErrorReset();
750 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeaturesJson;
751 :
752 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
753 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=PATCH");
754 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
755 : aosHTTPOptionsInt.AddString(
756 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
757 :
758 0 : CPLDebug("NGW", "PatchFeatures request payload: %s",
759 : osFeaturesJson.c_str());
760 :
761 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
762 0 : CPLJSONDocument oPatchFeatureReq;
763 0 : bool bResult = oPatchFeatureReq.LoadUrl(osUrlInt, aosHTTPOptionsInt);
764 :
765 0 : CPLJSONObject oRoot = oPatchFeatureReq.GetRoot();
766 0 : if (CheckRequestResult(bResult, oRoot, "Patch features failed"))
767 : {
768 0 : CPLJSONArray aoJSONIDs = oRoot.ToArray();
769 0 : for (int i = 0; i < aoJSONIDs.Size(); ++i)
770 : {
771 0 : GIntBig nOutFID = aoJSONIDs[i].GetLong("id", OGRNullFID);
772 0 : aoFIDs.push_back(nOutFID);
773 : }
774 : }
775 0 : return aoFIDs;
776 : }
777 :
778 0 : bool GetExtent(const std::string &osUrl, const std::string &osResourceId,
779 : const CPLStringList &aosHTTPOptions, int nEPSG,
780 : OGREnvelope &stExtent)
781 : {
782 0 : CPLErrorReset();
783 0 : CPLJSONDocument oExtentReq;
784 : double dfRetryDelaySecs =
785 0 : CPLAtof(aosHTTPOptions.FetchNameValueDef("RETRY_DELAY", "2.5"));
786 0 : int nMaxRetries = atoi(aosHTTPOptions.FetchNameValueDef("MAX_RETRY", "0"));
787 0 : int nRetryCount = 0;
788 : while (true)
789 : {
790 0 : auto osUrlNew = GetLayerExtent(osUrl, osResourceId);
791 0 : bool bResult = oExtentReq.LoadUrl(osUrlNew, aosHTTPOptions);
792 :
793 0 : CPLJSONObject oRoot = oExtentReq.GetRoot();
794 0 : if (CheckRequestResult(bResult, oRoot, "Get extent failed"))
795 : {
796 : // Response extent spatial reference is EPSG:4326.
797 :
798 0 : double dfMinX = oRoot.GetDouble("extent/minLon");
799 0 : double dfMinY = oRoot.GetDouble("extent/minLat");
800 0 : double dfMaxX = oRoot.GetDouble("extent/maxLon");
801 0 : double dfMaxY = oRoot.GetDouble("extent/maxLat");
802 :
803 : double adfCoordinatesX[4];
804 : double adfCoordinatesY[4];
805 0 : adfCoordinatesX[0] = dfMinX;
806 0 : adfCoordinatesY[0] = dfMinY;
807 0 : adfCoordinatesX[1] = dfMinX;
808 0 : adfCoordinatesY[1] = dfMaxY;
809 0 : adfCoordinatesX[2] = dfMaxX;
810 0 : adfCoordinatesY[2] = dfMaxY;
811 0 : adfCoordinatesX[3] = dfMaxX;
812 0 : adfCoordinatesY[3] = dfMinY;
813 :
814 0 : OGRSpatialReference o4326SRS;
815 0 : o4326SRS.SetWellKnownGeogCS("WGS84");
816 0 : o4326SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
817 0 : OGRSpatialReference o3857SRS;
818 0 : o3857SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
819 0 : if (o3857SRS.importFromEPSG(nEPSG) != OGRERR_NONE)
820 : {
821 0 : CPLError(CE_Failure, CPLE_AppDefined,
822 : "Project extent SRS to EPSG:3857 failed");
823 0 : return false;
824 : }
825 :
826 : OGRCoordinateTransformation *poTransform =
827 0 : OGRCreateCoordinateTransformation(&o4326SRS, &o3857SRS);
828 0 : if (poTransform)
829 : {
830 0 : poTransform->Transform(4, adfCoordinatesX, adfCoordinatesY);
831 0 : delete poTransform;
832 :
833 0 : stExtent.MinX = std::numeric_limits<double>::max();
834 0 : stExtent.MaxX = std::numeric_limits<double>::min();
835 0 : stExtent.MinY = std::numeric_limits<double>::max();
836 0 : stExtent.MaxY = std::numeric_limits<double>::min();
837 :
838 0 : for (int i = 1; i < 4; ++i)
839 : {
840 0 : if (stExtent.MinX > adfCoordinatesX[i])
841 : {
842 0 : stExtent.MinX = adfCoordinatesX[i];
843 : }
844 0 : if (stExtent.MaxX < adfCoordinatesX[i])
845 : {
846 0 : stExtent.MaxX = adfCoordinatesX[i];
847 : }
848 0 : if (stExtent.MinY > adfCoordinatesY[i])
849 : {
850 0 : stExtent.MinY = adfCoordinatesY[i];
851 : }
852 0 : if (stExtent.MaxY < adfCoordinatesY[i])
853 : {
854 0 : stExtent.MaxY = adfCoordinatesY[i];
855 : }
856 : }
857 : }
858 0 : CPLErrorReset(); // If we are here no error occurred
859 0 : return true;
860 : }
861 :
862 0 : if (nRetryCount >= nMaxRetries)
863 : {
864 0 : return false;
865 : }
866 :
867 0 : CPLSleep(dfRetryDelaySecs);
868 0 : nRetryCount++;
869 0 : }
870 : return false;
871 : }
872 :
873 0 : CPLJSONObject UploadFile(const std::string &osUrl,
874 : const std::string &osFilePath,
875 : const CPLStringList &aosHTTPOptions,
876 : GDALProgressFunc pfnProgress, void *pProgressData)
877 : {
878 0 : CPLErrorReset();
879 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
880 : aosHTTPOptionsInt.AddString(
881 0 : CPLSPrintf("FORM_FILE_PATH=%s", osFilePath.c_str()));
882 0 : aosHTTPOptionsInt.AddString("FORM_FILE_NAME=file");
883 :
884 0 : const char *pszFormFileName = CPLGetFilename(osFilePath.c_str());
885 0 : aosHTTPOptionsInt.AddString("FORM_KEY_0=name");
886 0 : aosHTTPOptionsInt.AddString(CPLSPrintf("FORM_VALUE_0=%s", pszFormFileName));
887 0 : aosHTTPOptionsInt.AddString("FORM_ITEM_COUNT=1");
888 :
889 : CPLHTTPResult *psResult =
890 0 : CPLHTTPFetchEx(GetUploadURL(osUrl).c_str(), aosHTTPOptionsInt,
891 0 : pfnProgress, pProgressData, nullptr, nullptr);
892 0 : CPLJSONObject oResult;
893 0 : if (psResult)
894 : {
895 0 : const bool bResult =
896 0 : psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
897 :
898 : // Get error message.
899 0 : if (!bResult)
900 : {
901 0 : ReportError(psResult->pabyData, psResult->nDataLen,
902 : "Upload file request failed");
903 : }
904 : else
905 : {
906 0 : CPLJSONDocument oFileJson;
907 0 : if (oFileJson.LoadMemory(psResult->pabyData, psResult->nDataLen))
908 : {
909 0 : oResult = oFileJson.GetRoot();
910 : }
911 : }
912 0 : CPLHTTPDestroyResult(psResult);
913 : }
914 : else
915 : {
916 0 : CPLError(CE_Failure, CPLE_AppDefined, "Upload file %s failed",
917 : osFilePath.c_str());
918 : }
919 0 : return oResult;
920 : }
921 :
922 : } // namespace NGWAPI
|