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, char **papszMetadata)
572 : {
573 0 : CPLJSONObject oResMeta("resmeta", oRoot);
574 0 : CPLJSONObject oResMetaItems("items", oResMeta);
575 0 : CPLStringList oaMetadata(papszMetadata, FALSE);
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 : char **papszMetadata, const CPLStringList &aosHTTPOptions)
611 : {
612 0 : if (nullptr == papszMetadata)
613 : {
614 0 : return true;
615 : }
616 0 : CPLJSONObject oMetadataJson;
617 0 : FillResmeta(oMetadataJson, papszMetadata);
618 :
619 0 : return UpdateResource(
620 : osUrl, osResourceId,
621 0 : oMetadataJson.Format(CPLJSONObject::PrettyFormat::Plain),
622 0 : aosHTTPOptions);
623 : }
624 :
625 0 : bool DeleteFeature(const std::string &osUrl, const std::string &osResourceId,
626 : const std::string &osFeatureId,
627 : const CPLStringList &aosHTTPOptions)
628 : {
629 0 : CPLErrorReset();
630 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
631 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=DELETE");
632 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId) + osFeatureId;
633 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
634 0 : bool bResult = false;
635 0 : if (psResult)
636 : {
637 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
638 : // Get error message.
639 0 : if (!bResult)
640 : {
641 0 : ReportError(psResult->pabyData, psResult->nDataLen,
642 : "DeleteFeature request failed");
643 : }
644 0 : CPLHTTPDestroyResult(psResult);
645 : }
646 0 : return bResult;
647 : }
648 :
649 0 : bool DeleteFeatures(const std::string &osUrl, const std::string &osResourceId,
650 : const std::string &osFeaturesIDJson,
651 : const CPLStringList &aosHTTPOptions)
652 : {
653 0 : CPLErrorReset();
654 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeaturesIDJson;
655 :
656 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
657 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=DELETE");
658 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
659 : aosHTTPOptionsInt.AddString(
660 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
661 :
662 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
663 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
664 0 : bool bResult = false;
665 0 : if (psResult)
666 : {
667 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
668 : // Get error message.
669 0 : if (!bResult)
670 : {
671 0 : ReportError(psResult->pabyData, psResult->nDataLen,
672 : "DeleteFeatures request failed");
673 : }
674 0 : CPLHTTPDestroyResult(psResult);
675 : }
676 0 : return bResult;
677 : }
678 :
679 0 : GIntBig CreateFeature(const std::string &osUrl, const std::string &osResourceId,
680 : const std::string &osFeatureJson,
681 : const CPLStringList &aosHTTPOptions)
682 : {
683 0 : CPLErrorReset();
684 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeatureJson;
685 :
686 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
687 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=POST");
688 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
689 : aosHTTPOptionsInt.AddString(
690 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
691 :
692 0 : CPLDebug("NGW", "CreateFeature request payload: %s", osFeatureJson.c_str());
693 :
694 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
695 :
696 0 : CPLJSONDocument oCreateFeatureReq;
697 0 : bool bResult = oCreateFeatureReq.LoadUrl(osUrlInt, aosHTTPOptionsInt);
698 :
699 0 : CPLJSONObject oRoot = oCreateFeatureReq.GetRoot();
700 0 : GIntBig nOutFID = OGRNullFID;
701 0 : if (CheckRequestResult(bResult, oRoot, "Create new feature failed"))
702 : {
703 0 : nOutFID = oRoot.GetLong("id", OGRNullFID);
704 : }
705 :
706 0 : CPLDebug("NGW", "CreateFeature new FID: " CPL_FRMT_GIB, nOutFID);
707 0 : return nOutFID;
708 : }
709 :
710 0 : bool UpdateFeature(const std::string &osUrl, const std::string &osResourceId,
711 : const std::string &osFeatureId,
712 : const std::string &osFeatureJson,
713 : const CPLStringList &aosHTTPOptions)
714 : {
715 0 : CPLErrorReset();
716 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeatureJson;
717 :
718 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
719 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=PUT");
720 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
721 : aosHTTPOptionsInt.AddString(
722 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
723 :
724 0 : CPLDebug("NGW", "UpdateFeature request payload: %s", osFeatureJson.c_str());
725 :
726 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId) + osFeatureId;
727 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osUrlInt.c_str(), aosHTTPOptionsInt);
728 0 : bool bResult = false;
729 0 : if (psResult)
730 : {
731 0 : bResult = psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
732 :
733 : // Get error message.
734 0 : if (!bResult)
735 : {
736 0 : ReportError(psResult->pabyData, psResult->nDataLen,
737 : "UpdateFeature request failed");
738 : }
739 0 : CPLHTTPDestroyResult(psResult);
740 : }
741 0 : return bResult;
742 : }
743 :
744 0 : std::vector<GIntBig> PatchFeatures(const std::string &osUrl,
745 : const std::string &osResourceId,
746 : const std::string &osFeaturesJson,
747 : const CPLStringList &aosHTTPOptions)
748 : {
749 0 : std::vector<GIntBig> aoFIDs;
750 0 : CPLErrorReset();
751 0 : std::string osPayloadInt = "POSTFIELDS=" + osFeaturesJson;
752 :
753 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
754 0 : aosHTTPOptionsInt.AddString("CUSTOMREQUEST=PATCH");
755 0 : aosHTTPOptionsInt.AddString(osPayloadInt.c_str());
756 : aosHTTPOptionsInt.AddString(
757 0 : "HEADERS=Content-Type: application/json\r\nAccept: */*");
758 :
759 0 : CPLDebug("NGW", "PatchFeatures request payload: %s",
760 : osFeaturesJson.c_str());
761 :
762 0 : std::string osUrlInt = GetFeatureURL(osUrl, osResourceId);
763 0 : CPLJSONDocument oPatchFeatureReq;
764 0 : bool bResult = oPatchFeatureReq.LoadUrl(osUrlInt, aosHTTPOptionsInt);
765 :
766 0 : CPLJSONObject oRoot = oPatchFeatureReq.GetRoot();
767 0 : if (CheckRequestResult(bResult, oRoot, "Patch features failed"))
768 : {
769 0 : CPLJSONArray aoJSONIDs = oRoot.ToArray();
770 0 : for (int i = 0; i < aoJSONIDs.Size(); ++i)
771 : {
772 0 : GIntBig nOutFID = aoJSONIDs[i].GetLong("id", OGRNullFID);
773 0 : aoFIDs.push_back(nOutFID);
774 : }
775 : }
776 0 : return aoFIDs;
777 : }
778 :
779 0 : bool GetExtent(const std::string &osUrl, const std::string &osResourceId,
780 : const CPLStringList &aosHTTPOptions, int nEPSG,
781 : OGREnvelope &stExtent)
782 : {
783 0 : CPLErrorReset();
784 0 : CPLJSONDocument oExtentReq;
785 : double dfRetryDelaySecs =
786 0 : CPLAtof(aosHTTPOptions.FetchNameValueDef("RETRY_DELAY", "2.5"));
787 0 : int nMaxRetries = atoi(aosHTTPOptions.FetchNameValueDef("MAX_RETRY", "0"));
788 0 : int nRetryCount = 0;
789 : while (true)
790 : {
791 0 : auto osUrlNew = GetLayerExtent(osUrl, osResourceId);
792 0 : bool bResult = oExtentReq.LoadUrl(osUrlNew, aosHTTPOptions);
793 :
794 0 : CPLJSONObject oRoot = oExtentReq.GetRoot();
795 0 : if (CheckRequestResult(bResult, oRoot, "Get extent failed"))
796 : {
797 : // Response extent spatial reference is EPSG:4326.
798 :
799 0 : double dfMinX = oRoot.GetDouble("extent/minLon");
800 0 : double dfMinY = oRoot.GetDouble("extent/minLat");
801 0 : double dfMaxX = oRoot.GetDouble("extent/maxLon");
802 0 : double dfMaxY = oRoot.GetDouble("extent/maxLat");
803 :
804 : double adfCoordinatesX[4];
805 : double adfCoordinatesY[4];
806 0 : adfCoordinatesX[0] = dfMinX;
807 0 : adfCoordinatesY[0] = dfMinY;
808 0 : adfCoordinatesX[1] = dfMinX;
809 0 : adfCoordinatesY[1] = dfMaxY;
810 0 : adfCoordinatesX[2] = dfMaxX;
811 0 : adfCoordinatesY[2] = dfMaxY;
812 0 : adfCoordinatesX[3] = dfMaxX;
813 0 : adfCoordinatesY[3] = dfMinY;
814 :
815 0 : OGRSpatialReference o4326SRS;
816 0 : o4326SRS.SetWellKnownGeogCS("WGS84");
817 0 : o4326SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
818 0 : OGRSpatialReference o3857SRS;
819 0 : o3857SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
820 0 : if (o3857SRS.importFromEPSG(nEPSG) != OGRERR_NONE)
821 : {
822 0 : CPLError(CE_Failure, CPLE_AppDefined,
823 : "Project extent SRS to EPSG:3857 failed");
824 0 : return false;
825 : }
826 :
827 : OGRCoordinateTransformation *poTransform =
828 0 : OGRCreateCoordinateTransformation(&o4326SRS, &o3857SRS);
829 0 : if (poTransform)
830 : {
831 0 : poTransform->Transform(4, adfCoordinatesX, adfCoordinatesY);
832 0 : delete poTransform;
833 :
834 0 : stExtent.MinX = std::numeric_limits<double>::max();
835 0 : stExtent.MaxX = std::numeric_limits<double>::min();
836 0 : stExtent.MinY = std::numeric_limits<double>::max();
837 0 : stExtent.MaxY = std::numeric_limits<double>::min();
838 :
839 0 : for (int i = 1; i < 4; ++i)
840 : {
841 0 : if (stExtent.MinX > adfCoordinatesX[i])
842 : {
843 0 : stExtent.MinX = adfCoordinatesX[i];
844 : }
845 0 : if (stExtent.MaxX < adfCoordinatesX[i])
846 : {
847 0 : stExtent.MaxX = adfCoordinatesX[i];
848 : }
849 0 : if (stExtent.MinY > adfCoordinatesY[i])
850 : {
851 0 : stExtent.MinY = adfCoordinatesY[i];
852 : }
853 0 : if (stExtent.MaxY < adfCoordinatesY[i])
854 : {
855 0 : stExtent.MaxY = adfCoordinatesY[i];
856 : }
857 : }
858 : }
859 0 : CPLErrorReset(); // If we are here no error occurred
860 0 : return true;
861 : }
862 :
863 0 : if (nRetryCount >= nMaxRetries)
864 : {
865 0 : break;
866 : }
867 :
868 0 : CPLSleep(dfRetryDelaySecs);
869 0 : nRetryCount++;
870 0 : }
871 0 : return false;
872 : }
873 :
874 0 : CPLJSONObject UploadFile(const std::string &osUrl,
875 : const std::string &osFilePath,
876 : const CPLStringList &aosHTTPOptions,
877 : GDALProgressFunc pfnProgress, void *pProgressData)
878 : {
879 0 : CPLErrorReset();
880 0 : CPLStringList aosHTTPOptionsInt(aosHTTPOptions);
881 : aosHTTPOptionsInt.AddString(
882 0 : CPLSPrintf("FORM_FILE_PATH=%s", osFilePath.c_str()));
883 0 : aosHTTPOptionsInt.AddString("FORM_FILE_NAME=file");
884 :
885 0 : const char *pszFormFileName = CPLGetFilename(osFilePath.c_str());
886 0 : aosHTTPOptionsInt.AddString("FORM_KEY_0=name");
887 0 : aosHTTPOptionsInt.AddString(CPLSPrintf("FORM_VALUE_0=%s", pszFormFileName));
888 0 : aosHTTPOptionsInt.AddString("FORM_ITEM_COUNT=1");
889 :
890 : CPLHTTPResult *psResult =
891 0 : CPLHTTPFetchEx(GetUploadURL(osUrl).c_str(), aosHTTPOptionsInt,
892 0 : pfnProgress, pProgressData, nullptr, nullptr);
893 0 : CPLJSONObject oResult;
894 0 : if (psResult)
895 : {
896 0 : const bool bResult =
897 0 : psResult->nStatus == 0 && psResult->pszErrBuf == nullptr;
898 :
899 : // Get error message.
900 0 : if (!bResult)
901 : {
902 0 : ReportError(psResult->pabyData, psResult->nDataLen,
903 : "Upload file request failed");
904 : }
905 : else
906 : {
907 0 : CPLJSONDocument oFileJson;
908 0 : if (oFileJson.LoadMemory(psResult->pabyData, psResult->nDataLen))
909 : {
910 0 : oResult = oFileJson.GetRoot();
911 : }
912 : }
913 0 : CPLHTTPDestroyResult(psResult);
914 : }
915 : else
916 : {
917 0 : CPLError(CE_Failure, CPLE_AppDefined, "Upload file %s failed",
918 : osFilePath.c_str());
919 : }
920 0 : return oResult;
921 : }
922 :
923 : } // namespace NGWAPI
|