Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implementation of private utilities used within OGR GeoJSON Driver.
5 : * Author: Mateusz Loskot, mateusz@loskot.net
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007, Mateusz Loskot
9 : * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogrgeojsonutils.h"
31 : #include <assert.h>
32 : #include "cpl_port.h"
33 : #include "cpl_conv.h"
34 : #include "ogr_geometry.h"
35 : #include <json.h> // JSON-C
36 :
37 : #include <algorithm>
38 : #include <memory>
39 :
40 : const char szESRIJSonPotentialStart1[] =
41 : "{\"features\":[{\"geometry\":{\"rings\":[";
42 :
43 : /************************************************************************/
44 : /* IsJSONObject() */
45 : /************************************************************************/
46 :
47 229229 : static bool IsJSONObject(const char *pszText)
48 : {
49 229229 : if (nullptr == pszText)
50 0 : return false;
51 :
52 : /* Skip UTF-8 BOM (#5630) */
53 229229 : const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
54 229229 : if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
55 6 : pszText += 3;
56 :
57 : /* -------------------------------------------------------------------- */
58 : /* This is a primitive test, but we need to perform it fast. */
59 : /* -------------------------------------------------------------------- */
60 230991 : while (*pszText != '\0' && isspace(static_cast<unsigned char>(*pszText)))
61 1762 : pszText++;
62 :
63 229229 : const char *const apszPrefix[] = {"loadGeoJSON(", "jsonp("};
64 687666 : for (size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++)
65 : {
66 458447 : if (strncmp(pszText, apszPrefix[iP], strlen(apszPrefix[iP])) == 0)
67 : {
68 10 : pszText += strlen(apszPrefix[iP]);
69 10 : break;
70 : }
71 : }
72 :
73 229229 : if (*pszText != '{')
74 225187 : return false;
75 :
76 4042 : return true;
77 : }
78 :
79 : /************************************************************************/
80 : /* IsTypeSomething() */
81 : /************************************************************************/
82 :
83 4532 : static bool IsTypeSomething(const char *pszText, const char *pszTypeValue)
84 : {
85 4532 : const char *pszIter = pszText;
86 : while (true)
87 : {
88 14022 : pszIter = strstr(pszIter, "\"type\"");
89 14022 : if (pszIter == nullptr)
90 4532 : return false;
91 10833 : pszIter += strlen("\"type\"");
92 10999 : while (isspace(static_cast<unsigned char>(*pszIter)))
93 166 : pszIter++;
94 10833 : if (*pszIter != ':')
95 0 : return false;
96 10833 : pszIter++;
97 20165 : while (isspace(static_cast<unsigned char>(*pszIter)))
98 9332 : pszIter++;
99 10833 : CPLString osValue;
100 10833 : osValue.Printf("\"%s\"", pszTypeValue);
101 10833 : if (STARTS_WITH(pszIter, osValue.c_str()))
102 1343 : return true;
103 9490 : }
104 : }
105 :
106 : /************************************************************************/
107 : /* GetCompactJSon() */
108 : /************************************************************************/
109 :
110 2534 : static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize)
111 : {
112 : /* Skip UTF-8 BOM (#5630) */
113 2534 : const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
114 2534 : if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
115 6 : pszText += 3;
116 :
117 2534 : CPLString osWithoutSpace;
118 2534 : bool bInString = false;
119 1729220 : for (int i = 0; pszText[i] != '\0' && osWithoutSpace.size() < nMaxSize; i++)
120 : {
121 1726680 : if (bInString)
122 : {
123 696653 : if (pszText[i] == '\\')
124 : {
125 239 : osWithoutSpace += pszText[i];
126 239 : if (pszText[i + 1] == '\0')
127 0 : break;
128 239 : osWithoutSpace += pszText[i + 1];
129 239 : i++;
130 : }
131 696414 : else if (pszText[i] == '"')
132 : {
133 49966 : bInString = false;
134 49966 : osWithoutSpace += '"';
135 : }
136 : else
137 : {
138 646448 : osWithoutSpace += pszText[i];
139 : }
140 : }
141 1030030 : else if (pszText[i] == '"')
142 : {
143 50110 : bInString = true;
144 50110 : osWithoutSpace += '"';
145 : }
146 979919 : else if (!isspace(static_cast<unsigned char>(pszText[i])))
147 : {
148 634393 : osWithoutSpace += pszText[i];
149 : }
150 : }
151 2534 : return osWithoutSpace;
152 : }
153 :
154 : /************************************************************************/
155 : /* IsGeoJSONLikeObject() */
156 : /************************************************************************/
157 :
158 97523 : static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence,
159 : bool &bReadMoreBytes)
160 : {
161 97523 : bMightBeSequence = false;
162 97523 : bReadMoreBytes = false;
163 :
164 97523 : if (!IsJSONObject(pszText))
165 95835 : return false;
166 :
167 1688 : if (IsTypeSomething(pszText, "Topology"))
168 6 : return false;
169 :
170 1682 : if (JSONFGIsObject(pszText) && GDALGetDriverByName("JSONFG"))
171 274 : return false;
172 :
173 1408 : if (IsTypeSomething(pszText, "FeatureCollection"))
174 : {
175 1004 : return true;
176 : }
177 :
178 808 : CPLString osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
179 409 : if (osWithoutSpace.find("{\"features\":[") == 0 &&
180 5 : osWithoutSpace.find(szESRIJSonPotentialStart1) != 0)
181 : {
182 3 : return true;
183 : }
184 :
185 : // See
186 : // https://raw.githubusercontent.com/icepack/icepack-data/master/meshes/larsen/larsen_inflow.geojson
187 : // "{"crs":...,"features":[..."
188 : // or
189 : // https://gist.githubusercontent.com/NiklasDallmann/27e339dd78d1942d524fbcd179f9fdcf/raw/527a8319d75a9e29446a32a19e4c902213b0d668/42XR9nLAh8Poh9Xmniqh3Bs9iisNm74mYMC56v3Wfyo=_isochrones_fails.geojson
190 : // "{"bbox":...,"features":[..."
191 401 : if (osWithoutSpace.find(",\"features\":[") != std::string::npos)
192 : {
193 30 : return !ESRIJSONIsObject(pszText);
194 : }
195 :
196 : // See https://github.com/OSGeo/gdal/issues/2720
197 739 : if (osWithoutSpace.find("{\"coordinates\":[") == 0 ||
198 : // and https://github.com/OSGeo/gdal/issues/2787
199 368 : osWithoutSpace.find("{\"geometry\":{\"coordinates\":[") == 0)
200 : {
201 6 : return true;
202 : }
203 :
204 365 : if (IsTypeSomething(pszText, "Feature") ||
205 218 : IsTypeSomething(pszText, "Point") ||
206 188 : IsTypeSomething(pszText, "LineString") ||
207 182 : IsTypeSomething(pszText, "Polygon") ||
208 176 : IsTypeSomething(pszText, "MultiPoint") ||
209 56 : IsTypeSomething(pszText, "MultiLineString") ||
210 621 : IsTypeSomething(pszText, "MultiPolygon") ||
211 38 : IsTypeSomething(pszText, "GeometryCollection"))
212 : {
213 327 : bMightBeSequence = true;
214 327 : return true;
215 : }
216 :
217 : // See https://github.com/OSGeo/gdal/issues/3280
218 38 : if (osWithoutSpace.find("{\"properties\":{") == 0)
219 : {
220 2 : bMightBeSequence = true;
221 2 : bReadMoreBytes = true;
222 2 : return false;
223 : }
224 :
225 36 : return false;
226 : }
227 :
228 26 : static bool IsGeoJSONLikeObject(const char *pszText)
229 : {
230 : bool bMightBeSequence;
231 : bool bReadMoreBytes;
232 52 : return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes);
233 : }
234 :
235 : /************************************************************************/
236 : /* ESRIJSONIsObject() */
237 : /************************************************************************/
238 :
239 44336 : bool ESRIJSONIsObject(const char *pszText)
240 : {
241 44336 : if (!IsJSONObject(pszText))
242 44115 : return false;
243 :
244 221 : if ( // ESRI Json geometry
245 221 : (strstr(pszText, "\"geometryType\"") != nullptr &&
246 64 : strstr(pszText, "\"esriGeometry") != nullptr)
247 :
248 : // ESRI Json "FeatureCollection"
249 157 : || strstr(pszText, "\"fieldAliases\"") != nullptr
250 :
251 : // ESRI Json "FeatureCollection"
252 157 : || (strstr(pszText, "\"fields\"") != nullptr &&
253 0 : strstr(pszText, "\"esriFieldType") != nullptr))
254 : {
255 64 : return true;
256 : }
257 :
258 : CPLString osWithoutSpace =
259 314 : GetCompactJSon(pszText, strlen(szESRIJSonPotentialStart1));
260 157 : if (osWithoutSpace.find(szESRIJSonPotentialStart1) == 0)
261 : {
262 0 : return true;
263 : }
264 :
265 157 : return false;
266 : }
267 :
268 : /************************************************************************/
269 : /* TopoJSONIsObject() */
270 : /************************************************************************/
271 :
272 44274 : bool TopoJSONIsObject(const char *pszText)
273 : {
274 44274 : if (!IsJSONObject(pszText))
275 44114 : return false;
276 :
277 160 : return IsTypeSomething(pszText, "Topology");
278 : }
279 :
280 : /************************************************************************/
281 : /* IsLikelyNewlineSequenceGeoJSON() */
282 : /************************************************************************/
283 :
284 299 : static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL,
285 : const GByte *pabyHeader,
286 : const char *pszFileContent)
287 : {
288 299 : const size_t nBufferSize = 4096 * 10;
289 299 : std::vector<GByte> abyBuffer;
290 299 : abyBuffer.resize(nBufferSize + 1);
291 :
292 299 : int nCurlLevel = 0;
293 299 : bool bInString = false;
294 299 : bool bLastIsEscape = false;
295 299 : bool bCompatibleOfSequence = true;
296 299 : bool bFirstIter = true;
297 299 : bool bEOLFound = false;
298 299 : int nCountObject = 0;
299 : while (true)
300 : {
301 : size_t nRead;
302 369 : bool bEnd = false;
303 369 : if (bFirstIter)
304 : {
305 299 : const char *pszText =
306 299 : pszFileContent ? pszFileContent
307 : : reinterpret_cast<const char *>(pabyHeader);
308 299 : assert(pszText);
309 299 : nRead = std::min(strlen(pszText), nBufferSize);
310 299 : memcpy(abyBuffer.data(), pszText, nRead);
311 299 : bFirstIter = false;
312 299 : if (fpL)
313 : {
314 131 : VSIFSeekL(fpL, nRead, SEEK_SET);
315 : }
316 : }
317 : else
318 : {
319 70 : nRead = VSIFReadL(abyBuffer.data(), 1, nBufferSize, fpL);
320 70 : bEnd = nRead < nBufferSize;
321 : }
322 1198580 : for (size_t i = 0; i < nRead; i++)
323 : {
324 1198310 : if (nCurlLevel == 0)
325 : {
326 554 : if (abyBuffer[i] == '{')
327 : {
328 389 : nCountObject++;
329 389 : if (nCountObject == 2)
330 : {
331 93 : break;
332 : }
333 296 : nCurlLevel++;
334 : }
335 165 : else if (nCountObject == 1 && abyBuffer[i] == '\n')
336 : {
337 111 : bEOLFound = true;
338 : }
339 54 : else if (!isspace(static_cast<unsigned char>(abyBuffer[i])))
340 : {
341 6 : bCompatibleOfSequence = false;
342 6 : break;
343 : }
344 : }
345 1197760 : else if (bInString)
346 : {
347 61104 : if (bLastIsEscape)
348 : {
349 18 : bLastIsEscape = false;
350 : }
351 61086 : else if (abyBuffer[i] == '\\')
352 : {
353 18 : bLastIsEscape = true;
354 : }
355 61068 : else if (abyBuffer[i] == '"')
356 : {
357 1335 : bInString = false;
358 : }
359 : }
360 1136650 : else if (abyBuffer[i] == '"')
361 : {
362 1335 : bInString = true;
363 : }
364 1135320 : else if (abyBuffer[i] == '{')
365 : {
366 194 : nCurlLevel++;
367 : }
368 1135120 : else if (abyBuffer[i] == '}')
369 : {
370 490 : nCurlLevel--;
371 : }
372 : }
373 369 : if (!fpL || bEnd || !bCompatibleOfSequence || nCountObject == 2)
374 : break;
375 70 : }
376 598 : return bCompatibleOfSequence && bEOLFound && nCountObject == 2;
377 : }
378 :
379 : /************************************************************************/
380 : /* GeoJSONFileIsObject() */
381 : /************************************************************************/
382 :
383 44869 : static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
384 : {
385 : // By default read first 6000 bytes.
386 : // 6000 was chosen as enough bytes to
387 : // enable all current tests to pass.
388 :
389 44869 : if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
390 : {
391 40827 : return false;
392 : }
393 :
394 4042 : bool bMightBeSequence = false;
395 4042 : bool bReadMoreBytes = false;
396 4042 : if (!IsGeoJSONLikeObject(
397 4042 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
398 : bMightBeSequence, bReadMoreBytes))
399 : {
400 3476 : if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
401 2 : poOpenInfo->TryToIngest(1000 * 1000) &&
402 2 : !IsGeoJSONLikeObject(
403 2 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
404 : bMightBeSequence, bReadMoreBytes)))
405 : {
406 3474 : return false;
407 : }
408 : }
409 :
410 643 : return !(bMightBeSequence &&
411 75 : IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL,
412 643 : poOpenInfo->pabyHeader, nullptr));
413 : }
414 :
415 : /************************************************************************/
416 : /* GeoJSONIsObject() */
417 : /************************************************************************/
418 :
419 45565 : bool GeoJSONIsObject(const char *pszText)
420 : {
421 45565 : bool bMightBeSequence = false;
422 45565 : bool bReadMoreBytes = false;
423 45565 : if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
424 : {
425 44883 : return false;
426 : }
427 :
428 844 : return !(bMightBeSequence &&
429 844 : IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText));
430 : }
431 :
432 : /************************************************************************/
433 : /* GeoJSONSeqFileIsObject() */
434 : /************************************************************************/
435 :
436 44354 : static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo)
437 : {
438 : // By default read first 6000 bytes.
439 : // 6000 was chosen as enough bytes to
440 : // enable all current tests to pass.
441 :
442 44354 : if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
443 : {
444 40817 : return false;
445 : }
446 :
447 3537 : const char *pszText =
448 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
449 3537 : if (pszText[0] == '\x1e')
450 26 : return IsGeoJSONLikeObject(pszText + 1);
451 :
452 3511 : bool bMightBeSequence = false;
453 3511 : bool bReadMoreBytes = false;
454 3511 : if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
455 : {
456 3455 : if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
457 0 : poOpenInfo->TryToIngest(1000 * 1000) &&
458 0 : IsGeoJSONLikeObject(
459 0 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
460 : bMightBeSequence, bReadMoreBytes)))
461 : {
462 3455 : return false;
463 : }
464 : }
465 :
466 112 : return bMightBeSequence &&
467 56 : IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL,
468 112 : poOpenInfo->pabyHeader, nullptr);
469 : }
470 :
471 44377 : bool GeoJSONSeqIsObject(const char *pszText)
472 : {
473 44377 : if (pszText[0] == '\x1e')
474 0 : return IsGeoJSONLikeObject(pszText + 1);
475 :
476 44377 : bool bMightBeSequence = false;
477 44377 : bool bReadMoreBytes = false;
478 44377 : if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
479 : {
480 44371 : return false;
481 : }
482 :
483 12 : return bMightBeSequence &&
484 12 : IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText);
485 : }
486 :
487 : /************************************************************************/
488 : /* JSONFGFileIsObject() */
489 : /************************************************************************/
490 :
491 40429 : static bool JSONFGFileIsObject(GDALOpenInfo *poOpenInfo)
492 : {
493 : // 6000 somewhat arbitrary. Based on other JSON-like drivers
494 40429 : if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
495 : {
496 39552 : return false;
497 : }
498 :
499 877 : const char *pszText =
500 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
501 877 : return JSONFGIsObject(pszText);
502 : }
503 :
504 43096 : bool JSONFGIsObject(const char *pszText)
505 : {
506 43096 : if (!IsJSONObject(pszText))
507 41123 : return false;
508 :
509 3946 : const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
510 :
511 : {
512 1973 : const auto nPos = osWithoutSpace.find("\"conformsTo\":[");
513 1973 : if (nPos != std::string::npos)
514 : {
515 548 : if (osWithoutSpace.find("\"[ogc-json-fg-1-0.1:core]\"", nPos) !=
516 548 : std::string::npos ||
517 0 : osWithoutSpace.find(
518 : "\"http://www.opengis.net/spec/json-fg-1/0.1\"", nPos) !=
519 : std::string::npos)
520 : {
521 548 : return true;
522 : }
523 : }
524 : }
525 :
526 2850 : if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos ||
527 2850 : osWithoutSpace.find("\"featureType\":\"") != std::string::npos ||
528 2850 : osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos ||
529 1425 : osWithoutSpace.find("\"place\":{\"coordinates\":") !=
530 1425 : std::string::npos ||
531 2850 : osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos ||
532 4275 : osWithoutSpace.find("\"time\":{\"timestamp\":") != std::string::npos ||
533 1425 : osWithoutSpace.find("\"time\":{\"interval\":") != std::string::npos)
534 : {
535 0 : return true;
536 : }
537 :
538 1425 : return false;
539 : }
540 :
541 : /************************************************************************/
542 : /* IsLikelyESRIJSONURL() */
543 : /************************************************************************/
544 :
545 115 : static bool IsLikelyESRIJSONURL(const char *pszURL)
546 : {
547 : // URLs with f=json are strong candidates for ESRI JSON services
548 : // except if they have "/items?", in which case they are likely OAPIF
549 115 : return strstr(pszURL, "f=json") != nullptr &&
550 115 : strstr(pszURL, "/items?") == nullptr;
551 : }
552 :
553 : /************************************************************************/
554 : /* GeoJSONGetSourceType() */
555 : /************************************************************************/
556 :
557 45184 : GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo)
558 : {
559 45184 : GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
560 :
561 : // NOTE: Sometimes URL ends with .geojson token, for example
562 : // http://example/path/2232.geojson
563 : // It's important to test beginning of source first.
564 45184 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:http://") ||
565 45185 : STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:https://") ||
566 45185 : STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:ftp://"))
567 : {
568 0 : srcType = eGeoJSONSourceService;
569 : }
570 45185 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
571 45142 : STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
572 45141 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
573 : {
574 44 : if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") ||
575 44 : strstr(poOpenInfo->pszFilename, "service=WFS") ||
576 44 : strstr(poOpenInfo->pszFilename, "service=wfs")) &&
577 0 : !strstr(poOpenInfo->pszFilename, "json"))
578 : {
579 0 : return eGeoJSONSourceUnknown;
580 : }
581 44 : if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
582 : {
583 0 : return eGeoJSONSourceUnknown;
584 : }
585 44 : srcType = eGeoJSONSourceService;
586 : }
587 45141 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:"))
588 : {
589 : VSIStatBufL sStat;
590 0 : if (VSIStatL(poOpenInfo->pszFilename + strlen("GeoJSON:"), &sStat) == 0)
591 : {
592 0 : return eGeoJSONSourceFile;
593 : }
594 0 : const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:");
595 0 : if (GeoJSONIsObject(pszText))
596 0 : return eGeoJSONSourceText;
597 0 : return eGeoJSONSourceUnknown;
598 : }
599 45141 : else if (GeoJSONIsObject(poOpenInfo->pszFilename))
600 : {
601 272 : srcType = eGeoJSONSourceText;
602 : }
603 44869 : else if (GeoJSONFileIsObject(poOpenInfo))
604 : {
605 540 : srcType = eGeoJSONSourceFile;
606 : }
607 :
608 45183 : return srcType;
609 : }
610 :
611 : /************************************************************************/
612 : /* ESRIJSONDriverGetSourceType() */
613 : /************************************************************************/
614 :
615 44299 : GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
616 : {
617 44299 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:http://") ||
618 44299 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:https://") ||
619 44300 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:ftp://"))
620 : {
621 0 : return eGeoJSONSourceService;
622 : }
623 44300 : else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
624 44292 : STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
625 44291 : STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
626 : {
627 9 : if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
628 : {
629 0 : return eGeoJSONSourceService;
630 : }
631 9 : return eGeoJSONSourceUnknown;
632 : }
633 :
634 44291 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:"))
635 : {
636 : VSIStatBufL sStat;
637 2 : if (VSIStatL(poOpenInfo->pszFilename + strlen("ESRIJSON:"), &sStat) ==
638 : 0)
639 : {
640 2 : return eGeoJSONSourceFile;
641 : }
642 0 : const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:");
643 0 : if (ESRIJSONIsObject(pszText))
644 0 : return eGeoJSONSourceText;
645 0 : return eGeoJSONSourceUnknown;
646 : }
647 :
648 44289 : if (poOpenInfo->fpL == nullptr)
649 : {
650 40818 : const char *pszText = poOpenInfo->pszFilename;
651 40818 : if (ESRIJSONIsObject(pszText))
652 4 : return eGeoJSONSourceText;
653 40814 : return eGeoJSONSourceUnknown;
654 : }
655 :
656 : // By default read first 6000 bytes.
657 : // 6000 was chosen as enough bytes to
658 : // enable all current tests to pass.
659 3471 : if (!poOpenInfo->TryToIngest(6000))
660 : {
661 0 : return eGeoJSONSourceUnknown;
662 : }
663 :
664 6942 : if (poOpenInfo->pabyHeader != nullptr &&
665 3471 : ESRIJSONIsObject(
666 3471 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader)))
667 : {
668 30 : return eGeoJSONSourceFile;
669 : }
670 3441 : return eGeoJSONSourceUnknown;
671 : }
672 :
673 : /************************************************************************/
674 : /* TopoJSONDriverGetSourceType() */
675 : /************************************************************************/
676 :
677 44268 : GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
678 : {
679 44268 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:http://") ||
680 44268 : STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:https://") ||
681 44268 : STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:ftp://"))
682 : {
683 0 : return eGeoJSONSourceService;
684 : }
685 44268 : else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
686 44259 : STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
687 44258 : STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
688 : {
689 10 : if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
690 : {
691 0 : return eGeoJSONSourceUnknown;
692 : }
693 10 : return eGeoJSONSourceService;
694 : }
695 :
696 44258 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:"))
697 : {
698 : VSIStatBufL sStat;
699 0 : if (VSIStatL(poOpenInfo->pszFilename + strlen("TopoJSON:"), &sStat) ==
700 : 0)
701 : {
702 0 : return eGeoJSONSourceFile;
703 : }
704 0 : const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:");
705 0 : if (TopoJSONIsObject(pszText))
706 0 : return eGeoJSONSourceText;
707 0 : return eGeoJSONSourceUnknown;
708 : }
709 :
710 44258 : if (poOpenInfo->fpL == nullptr)
711 : {
712 40813 : const char *pszText = poOpenInfo->pszFilename;
713 40813 : if (TopoJSONIsObject(pszText))
714 0 : return eGeoJSONSourceText;
715 40813 : return eGeoJSONSourceUnknown;
716 : }
717 :
718 : // By default read first 6000 bytes.
719 : // 6000 was chosen as enough bytes to
720 : // enable all current tests to pass.
721 3445 : if (!poOpenInfo->TryToIngest(6000))
722 : {
723 0 : return eGeoJSONSourceUnknown;
724 : }
725 :
726 6888 : if (poOpenInfo->pabyHeader != nullptr &&
727 3444 : TopoJSONIsObject(
728 3444 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader)))
729 : {
730 6 : return eGeoJSONSourceFile;
731 : }
732 3438 : return eGeoJSONSourceUnknown;
733 : }
734 :
735 : /************************************************************************/
736 : /* GeoJSONSeqGetSourceType() */
737 : /************************************************************************/
738 :
739 44404 : GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo)
740 : {
741 44404 : GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
742 :
743 44404 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:http://") ||
744 44404 : STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:https://") ||
745 44403 : STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:ftp://"))
746 : {
747 0 : srcType = eGeoJSONSourceService;
748 : }
749 44404 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
750 44364 : STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
751 44362 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
752 : {
753 42 : if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
754 : {
755 0 : return eGeoJSONSourceUnknown;
756 : }
757 42 : srcType = eGeoJSONSourceService;
758 : }
759 44362 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:"))
760 : {
761 : VSIStatBufL sStat;
762 2 : if (VSIStatL(poOpenInfo->pszFilename + strlen("GEOJSONSeq:"), &sStat) ==
763 : 0)
764 : {
765 2 : return eGeoJSONSourceFile;
766 : }
767 0 : const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:");
768 0 : if (GeoJSONSeqIsObject(pszText))
769 0 : return eGeoJSONSourceText;
770 0 : return eGeoJSONSourceUnknown;
771 : }
772 44360 : else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename))
773 : {
774 6 : srcType = eGeoJSONSourceText;
775 : }
776 44354 : else if (GeoJSONSeqFileIsObject(poOpenInfo))
777 : {
778 82 : srcType = eGeoJSONSourceFile;
779 : }
780 :
781 44402 : return srcType;
782 : }
783 :
784 : /************************************************************************/
785 : /* JSONFGDriverGetSourceType() */
786 : /************************************************************************/
787 :
788 40529 : GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo)
789 : {
790 40529 : GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
791 :
792 40529 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:http://") ||
793 40529 : STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:https://") ||
794 40529 : STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:ftp://"))
795 : {
796 0 : srcType = eGeoJSONSourceService;
797 : }
798 40529 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
799 40521 : STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
800 40519 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
801 : {
802 10 : if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
803 : {
804 0 : return eGeoJSONSourceUnknown;
805 : }
806 10 : srcType = eGeoJSONSourceService;
807 : }
808 40519 : else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:"))
809 : {
810 : VSIStatBufL sStat;
811 0 : const size_t nJSONFGPrefixLen = strlen("JSONFG:");
812 0 : if (VSIStatL(poOpenInfo->pszFilename + nJSONFGPrefixLen, &sStat) == 0)
813 : {
814 0 : return eGeoJSONSourceFile;
815 : }
816 0 : const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen;
817 0 : if (JSONFGIsObject(pszText))
818 0 : return eGeoJSONSourceText;
819 0 : return eGeoJSONSourceUnknown;
820 : }
821 40519 : else if (JSONFGIsObject(poOpenInfo->pszFilename))
822 : {
823 90 : srcType = eGeoJSONSourceText;
824 : }
825 40429 : else if (JSONFGFileIsObject(poOpenInfo))
826 : {
827 184 : srcType = eGeoJSONSourceFile;
828 : }
829 :
830 40529 : return srcType;
831 : }
832 :
833 : /************************************************************************/
834 : /* GeoJSONPropertyToFieldType() */
835 : /************************************************************************/
836 :
837 : constexpr GIntBig MY_INT64_MAX = (((GIntBig)0x7FFFFFFF) << 32) | 0xFFFFFFFF;
838 : constexpr GIntBig MY_INT64_MIN = ((GIntBig)0x80000000) << 32;
839 :
840 4649 : OGRFieldType GeoJSONPropertyToFieldType(json_object *poObject,
841 : OGRFieldSubType &eSubType,
842 : bool bArrayAsString)
843 : {
844 4649 : eSubType = OFSTNone;
845 :
846 4649 : if (poObject == nullptr)
847 : {
848 7 : return OFTString;
849 : }
850 :
851 4642 : json_type type = json_object_get_type(poObject);
852 :
853 4642 : if (json_type_boolean == type)
854 : {
855 31 : eSubType = OFSTBoolean;
856 31 : return OFTInteger;
857 : }
858 4611 : else if (json_type_double == type)
859 489 : return OFTReal;
860 4122 : else if (json_type_int == type)
861 : {
862 2437 : GIntBig nVal = json_object_get_int64(poObject);
863 2437 : if (!CPL_INT64_FITS_ON_INT32(nVal))
864 : {
865 34 : if (nVal == MY_INT64_MIN || nVal == MY_INT64_MAX)
866 : {
867 : static bool bWarned = false;
868 1 : if (!bWarned)
869 : {
870 1 : bWarned = true;
871 1 : CPLError(
872 : CE_Warning, CPLE_AppDefined,
873 : "Integer values probably ranging out of 64bit integer "
874 : "range have been found. Will be clamped to "
875 : "INT64_MIN/INT64_MAX");
876 : }
877 : }
878 34 : return OFTInteger64;
879 : }
880 : else
881 : {
882 2403 : return OFTInteger;
883 : }
884 : }
885 1685 : else if (json_type_string == type)
886 1356 : return OFTString;
887 329 : else if (json_type_array == type)
888 : {
889 300 : if (bArrayAsString)
890 : {
891 1 : eSubType = OFSTJSON;
892 1 : return OFTString;
893 : }
894 299 : const auto nSize = json_object_array_length(poObject);
895 299 : if (nSize == 0)
896 : {
897 1 : eSubType = OFSTJSON;
898 1 : return OFTString;
899 : }
900 298 : OGRFieldType eType = OFTIntegerList;
901 617 : for (auto i = decltype(nSize){0}; i < nSize; i++)
902 : {
903 332 : json_object *poRow = json_object_array_get_idx(poObject, i);
904 332 : if (poRow != nullptr)
905 : {
906 332 : type = json_object_get_type(poRow);
907 332 : if (type == json_type_string)
908 : {
909 70 : if (i == 0 || eType == OFTStringList)
910 : {
911 67 : eType = OFTStringList;
912 : }
913 : else
914 : {
915 3 : eSubType = OFSTJSON;
916 3 : return OFTString;
917 : }
918 : }
919 262 : else if (type == json_type_double)
920 : {
921 62 : if (eSubType == OFSTNone &&
922 6 : (i == 0 || eType == OFTRealList ||
923 2 : eType == OFTIntegerList || eType == OFTInteger64List))
924 : {
925 62 : eType = OFTRealList;
926 : }
927 : else
928 : {
929 0 : eSubType = OFSTJSON;
930 0 : return OFTString;
931 : }
932 : }
933 200 : else if (type == json_type_int)
934 : {
935 155 : if (eSubType == OFSTNone && eType == OFTIntegerList)
936 : {
937 144 : GIntBig nVal = json_object_get_int64(poRow);
938 144 : if (!CPL_INT64_FITS_ON_INT32(nVal))
939 144 : eType = OFTInteger64List;
940 : }
941 11 : else if (eSubType == OFSTNone &&
942 5 : (eType == OFTInteger64List ||
943 : eType == OFTRealList))
944 : {
945 : // ok
946 : }
947 : else
948 : {
949 3 : eSubType = OFSTJSON;
950 3 : return OFTString;
951 : }
952 : }
953 45 : else if (type == json_type_boolean)
954 : {
955 39 : if (i == 0 ||
956 4 : (eType == OFTIntegerList && eSubType == OFSTBoolean))
957 : {
958 38 : eSubType = OFSTBoolean;
959 : }
960 : else
961 : {
962 1 : eSubType = OFSTJSON;
963 1 : return OFTString;
964 : }
965 : }
966 : else
967 : {
968 6 : eSubType = OFSTJSON;
969 6 : return OFTString;
970 : }
971 : }
972 : else
973 : {
974 0 : eSubType = OFSTJSON;
975 0 : return OFTString;
976 : }
977 : }
978 :
979 285 : return eType;
980 : }
981 29 : else if (json_type_object == type)
982 : {
983 29 : eSubType = OFSTJSON;
984 29 : return OFTString;
985 : }
986 :
987 0 : return OFTString; // null
988 : }
989 :
990 : /************************************************************************/
991 : /* GeoJSONStringPropertyToFieldType() */
992 : /************************************************************************/
993 :
994 615 : OGRFieldType GeoJSONStringPropertyToFieldType(json_object *poObject,
995 : int &nTZFlag)
996 : {
997 615 : if (poObject == nullptr)
998 : {
999 7 : return OFTString;
1000 : }
1001 608 : const char *pszStr = json_object_get_string(poObject);
1002 :
1003 608 : nTZFlag = 0;
1004 : OGRField sWrkField;
1005 608 : CPLPushErrorHandler(CPLQuietErrorHandler);
1006 608 : const bool bSuccess = CPL_TO_BOOL(OGRParseDate(pszStr, &sWrkField, 0));
1007 608 : CPLPopErrorHandler();
1008 608 : CPLErrorReset();
1009 608 : if (bSuccess)
1010 : {
1011 225 : const bool bHasDate =
1012 225 : strchr(pszStr, '/') != nullptr || strchr(pszStr, '-') != nullptr;
1013 225 : const bool bHasTime = strchr(pszStr, ':') != nullptr;
1014 225 : nTZFlag = sWrkField.Date.TZFlag;
1015 225 : if (bHasDate && bHasTime)
1016 123 : return OFTDateTime;
1017 102 : else if (bHasDate)
1018 100 : return OFTDate;
1019 : else
1020 2 : return OFTTime;
1021 : // TODO: What if both are false?
1022 : }
1023 383 : return OFTString;
1024 : }
1025 :
1026 : /************************************************************************/
1027 : /* OGRGeoJSONGetGeometryName() */
1028 : /************************************************************************/
1029 :
1030 1646 : const char *OGRGeoJSONGetGeometryName(OGRGeometry const *poGeometry)
1031 : {
1032 1646 : CPLAssert(nullptr != poGeometry);
1033 :
1034 1646 : const OGRwkbGeometryType eType = wkbFlatten(poGeometry->getGeometryType());
1035 :
1036 1646 : if (wkbPoint == eType)
1037 161 : return "Point";
1038 1485 : else if (wkbLineString == eType)
1039 54 : return "LineString";
1040 1431 : else if (wkbPolygon == eType)
1041 1263 : return "Polygon";
1042 168 : else if (wkbMultiPoint == eType)
1043 66 : return "MultiPoint";
1044 102 : else if (wkbMultiLineString == eType)
1045 34 : return "MultiLineString";
1046 68 : else if (wkbMultiPolygon == eType)
1047 42 : return "MultiPolygon";
1048 26 : else if (wkbGeometryCollection == eType)
1049 25 : return "GeometryCollection";
1050 :
1051 1 : return "Unknown";
1052 : }
|