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