/src/postgis/liblwgeom/lwin_geojson.c
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * PostGIS - Spatial Types for PostgreSQL |
4 | | * http://postgis.net |
5 | | * |
6 | | * PostGIS is free software: you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation, either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * PostGIS is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with PostGIS. If not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | ********************************************************************** |
20 | | * |
21 | | * Copyright 2019 Darafei Praliaskouski <me@komzpa.net> |
22 | | * Copyright 2013 Sandro Santilli <strk@kbt.io> |
23 | | * Copyright 2011 Kashif Rasul <kashif.rasul@gmail.com> |
24 | | * |
25 | | **********************************************************************/ |
26 | | |
27 | | #include "liblwgeom.h" |
28 | | #include "lwgeom_log.h" |
29 | | #include "../postgis_config.h" |
30 | | |
31 | | #if defined(HAVE_LIBJSON) |
32 | | |
33 | | #define JSON_C_VERSION_013 (13 << 8) |
34 | | |
35 | | #include <json.h> |
36 | | |
37 | | #if !defined(JSON_C_VERSION_NUM) || JSON_C_VERSION_NUM < JSON_C_VERSION_013 |
38 | | #include <json_object_private.h> |
39 | | #endif |
40 | | |
41 | | #ifndef JSON_C_VERSION |
42 | | /* Adds support for libjson < 0.10 */ |
43 | | #define json_tokener_error_desc(x) json_tokener_errors[(x)] |
44 | | #endif |
45 | | |
46 | | #include <string.h> |
47 | | |
48 | | /* Prototype */ |
49 | | static LWGEOM *parse_geojson(json_object *geojson, int *hasz); |
50 | | |
51 | | static inline json_object * |
52 | | findMemberByName(json_object *poObj, const char *pszName) |
53 | 30.8k | { |
54 | 30.8k | json_object *poTmp; |
55 | 30.8k | json_object_iter it; |
56 | | |
57 | 30.8k | poTmp = poObj; |
58 | | |
59 | 30.8k | if (!pszName || !poObj) |
60 | 1 | return NULL; |
61 | | |
62 | 30.8k | it.key = NULL; |
63 | 30.8k | it.val = NULL; |
64 | 30.8k | it.entry = NULL; |
65 | | |
66 | 30.8k | if (json_object_get_object(poTmp)) |
67 | 30.8k | { |
68 | 30.8k | if (!json_object_get_object(poTmp)->head) |
69 | 7 | { |
70 | 7 | lwerror("invalid GeoJSON representation"); |
71 | 7 | return NULL; |
72 | 7 | } |
73 | | |
74 | 30.7k | for (it.entry = json_object_get_object(poTmp)->head; |
75 | 50.6k | (it.entry ? (it.key = (char *)it.entry->k, it.val = (json_object *)it.entry->v, it.entry) : 0); |
76 | 30.7k | it.entry = it.entry->next) |
77 | 47.9k | { |
78 | 47.9k | if (strcasecmp((char *)it.key, pszName) == 0) |
79 | 28.0k | return it.val; |
80 | 47.9k | } |
81 | 30.7k | } |
82 | | |
83 | 2.79k | return NULL; |
84 | 30.8k | } |
85 | | |
86 | | static inline json_object * |
87 | | parse_coordinates(json_object *geojson) |
88 | 11.3k | { |
89 | 11.3k | json_object *coordinates = findMemberByName(geojson, "coordinates"); |
90 | 11.3k | if (!coordinates) |
91 | 123 | { |
92 | 123 | lwerror("Unable to find 'coordinates' in GeoJSON string"); |
93 | 123 | return NULL; |
94 | 123 | } |
95 | | |
96 | 11.2k | if (json_type_array != json_object_get_type(coordinates)) |
97 | 9 | { |
98 | 9 | lwerror("The 'coordinates' in GeoJSON are not an array"); |
99 | 9 | return NULL; |
100 | 9 | } |
101 | 11.2k | return coordinates; |
102 | 11.2k | } |
103 | | |
104 | | |
105 | | static inline int |
106 | | parse_geojson_coord(json_object *poObj, int *hasz, POINTARRAY *pa) |
107 | 708k | { |
108 | 708k | POINT4D pt = {0, 0, 0, 0}; |
109 | | |
110 | 708k | if (json_object_get_type(poObj) == json_type_array) |
111 | 677k | { |
112 | 677k | json_object *poObjCoord = NULL; |
113 | 677k | const int nSize = json_object_array_length(poObj); |
114 | 677k | if (nSize == 0) |
115 | 534k | return LW_TRUE; |
116 | 142k | if (nSize < 2) |
117 | 211 | { |
118 | 211 | lwerror("Too few ordinates in GeoJSON"); |
119 | 211 | return LW_FAILURE; |
120 | 211 | } |
121 | | |
122 | | /* Read X coordinate */ |
123 | 142k | poObjCoord = json_object_array_get_idx(poObj, 0); |
124 | 142k | pt.x = json_object_get_double(poObjCoord); |
125 | | |
126 | | /* Read Y coordinate */ |
127 | 142k | poObjCoord = json_object_array_get_idx(poObj, 1); |
128 | 142k | pt.y = json_object_get_double(poObjCoord); |
129 | | |
130 | 142k | if (nSize > 2) /* should this be >= 3 ? */ |
131 | 696 | { |
132 | | /* Read Z coordinate */ |
133 | 696 | poObjCoord = json_object_array_get_idx(poObj, 2); |
134 | 696 | pt.z = json_object_get_double(poObjCoord); |
135 | 696 | *hasz = LW_TRUE; |
136 | 696 | } |
137 | 142k | } |
138 | 31.2k | else |
139 | 31.2k | { |
140 | | /* If it's not an array, just don't handle it */ |
141 | 31.2k | lwerror("The 'coordinates' in GeoJSON are not sufficiently nested"); |
142 | 31.2k | return LW_FAILURE; |
143 | 31.2k | } |
144 | | |
145 | 142k | return ptarray_append_point(pa, &pt, LW_TRUE); |
146 | 708k | } |
147 | | |
148 | | static inline LWGEOM * |
149 | | parse_geojson_point(json_object *geojson, int *hasz) |
150 | 5.23k | { |
151 | 5.23k | json_object *coords = parse_coordinates(geojson); |
152 | 5.23k | if (!coords) |
153 | 106 | return NULL; |
154 | 5.12k | POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); |
155 | 5.12k | parse_geojson_coord(coords, hasz, pa); |
156 | 5.12k | return (LWGEOM *)lwpoint_construct(0, NULL, pa); |
157 | 5.23k | } |
158 | | |
159 | | static inline LWGEOM * |
160 | | parse_geojson_linestring(json_object *geojson, int *hasz) |
161 | 522 | { |
162 | 522 | json_object *points = parse_coordinates(geojson); |
163 | 522 | if (!points) |
164 | 3 | return NULL; |
165 | 519 | POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); |
166 | 519 | const int nPoints = json_object_array_length(points); |
167 | 88.4k | for (int i = 0; i < nPoints; i++) |
168 | 87.9k | { |
169 | 87.9k | json_object *coords = json_object_array_get_idx(points, i); |
170 | 87.9k | parse_geojson_coord(coords, hasz, pa); |
171 | 87.9k | } |
172 | 519 | return (LWGEOM *)lwline_construct(0, NULL, pa); |
173 | 522 | } |
174 | | |
175 | | static inline LWPOLY * |
176 | | parse_geojson_poly_rings(json_object *rings, int *hasz) |
177 | 82.0k | { |
178 | 82.0k | if (!rings || json_object_get_type(rings) != json_type_array) |
179 | 12.8k | return NULL; |
180 | | |
181 | 69.2k | int nRings = json_object_array_length(rings); |
182 | | |
183 | | /* No rings => POLYGON EMPTY */ |
184 | 69.2k | if (!nRings) |
185 | 66.7k | return lwpoly_construct_empty(0, 1, 0); |
186 | | |
187 | | /* Expecting up to nRings otherwise */ |
188 | 2.47k | POINTARRAY **ppa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nRings); |
189 | 2.47k | int o = 0; |
190 | | |
191 | 287k | for (int i = 0; i < nRings; i++) |
192 | 285k | { |
193 | 285k | json_object *points = json_object_array_get_idx(rings, i); |
194 | 285k | if (!points || json_object_get_type(points) != json_type_array) |
195 | 654 | { |
196 | 16.8k | for (int k = 0; k < o; k++) |
197 | 16.2k | ptarray_free(ppa[k]); |
198 | 654 | lwfree(ppa); |
199 | 654 | lwerror("The 'coordinates' in GeoJSON ring are not an array"); |
200 | 654 | return NULL; |
201 | 654 | } |
202 | 285k | int nPoints = json_object_array_length(points); |
203 | | |
204 | | /* Skip empty rings */ |
205 | 285k | if (!nPoints) |
206 | 106k | { |
207 | | /* Empty outer? Don't promote first hole to outer, holes don't matter. */ |
208 | 106k | if (!i) |
209 | 201 | break; |
210 | 106k | else |
211 | 106k | continue; |
212 | 106k | } |
213 | | |
214 | 178k | ppa[o] = ptarray_construct_empty(1, 0, 1); |
215 | 531k | for (int j = 0; j < nPoints; j++) |
216 | 353k | { |
217 | 353k | json_object *coords = NULL; |
218 | 353k | coords = json_object_array_get_idx(points, j); |
219 | 353k | if (LW_FAILURE == parse_geojson_coord(coords, hasz, ppa[o])) |
220 | 248 | { |
221 | 1.19k | for (int k = 0; k <= o; k++) |
222 | 946 | ptarray_free(ppa[k]); |
223 | 248 | lwfree(ppa); |
224 | 248 | lwerror("The 'coordinates' in GeoJSON are not sufficiently nested"); |
225 | 248 | return NULL; |
226 | 248 | } |
227 | 353k | } |
228 | 178k | o++; |
229 | 178k | } |
230 | | |
231 | | /* All the rings were empty! */ |
232 | 1.57k | if (!o) |
233 | 201 | { |
234 | 201 | lwfree(ppa); |
235 | 201 | return lwpoly_construct_empty(0, 1, 0); |
236 | 201 | } |
237 | | |
238 | 1.37k | return lwpoly_construct(0, NULL, o, ppa); |
239 | 1.57k | } |
240 | | |
241 | | static inline LWGEOM * |
242 | | parse_geojson_polygon(json_object *geojson, int *hasz) |
243 | 646 | { |
244 | 646 | return (LWGEOM *)parse_geojson_poly_rings(parse_coordinates(geojson), hasz); |
245 | 646 | } |
246 | | |
247 | | static inline LWGEOM * |
248 | | parse_geojson_multipoint(json_object *geojson, int *hasz) |
249 | 2.76k | { |
250 | 2.76k | json_object *points = parse_coordinates(geojson); |
251 | 2.76k | if (!points) |
252 | 3 | return NULL; |
253 | 2.76k | LWMPOINT *geom = (LWMPOINT *)lwcollection_construct_empty(MULTIPOINTTYPE, 0, 1, 0); |
254 | | |
255 | 2.76k | const int nPoints = json_object_array_length(points); |
256 | 230k | for (int i = 0; i < nPoints; ++i) |
257 | 227k | { |
258 | 227k | POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); |
259 | 227k | json_object *coord = json_object_array_get_idx(points, i); |
260 | 227k | if (parse_geojson_coord(coord, hasz, pa)) |
261 | 227k | geom = lwmpoint_add_lwpoint(geom, lwpoint_construct(0, NULL, pa)); |
262 | 25 | else |
263 | 25 | { |
264 | 25 | lwmpoint_free(geom); |
265 | 25 | ptarray_free(pa); |
266 | 25 | return NULL; |
267 | 25 | } |
268 | 227k | } |
269 | | |
270 | 2.73k | return (LWGEOM *)geom; |
271 | 2.76k | } |
272 | | |
273 | | static inline LWGEOM * |
274 | | parse_geojson_multilinestring(json_object *geojson, int *hasz) |
275 | 952 | { |
276 | 952 | json_object *mls = parse_coordinates(geojson); |
277 | 952 | if (!mls) |
278 | 2 | return NULL; |
279 | 950 | LWMLINE *geom = (LWMLINE *)lwcollection_construct_empty(MULTILINETYPE, 0, 1, 0); |
280 | 950 | const int nLines = json_object_array_length(mls); |
281 | 27.6k | for (int i = 0; i < nLines; ++i) |
282 | 26.7k | { |
283 | 26.7k | POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); |
284 | 26.7k | json_object *coords = json_object_array_get_idx(mls, i); |
285 | | |
286 | 26.7k | if (json_type_array == json_object_get_type(coords)) |
287 | 26.7k | { |
288 | 26.7k | const int nPoints = json_object_array_length(coords); |
289 | 61.6k | for (int j = 0; j < nPoints; ++j) |
290 | 34.9k | { |
291 | 34.9k | json_object *coord = json_object_array_get_idx(coords, j); |
292 | 34.9k | if (!parse_geojson_coord(coord, hasz, pa)) |
293 | 18 | { |
294 | 18 | lwmline_free(geom); |
295 | 18 | ptarray_free(pa); |
296 | 18 | return NULL; |
297 | 18 | } |
298 | 34.9k | } |
299 | 26.7k | geom = lwmline_add_lwline(geom, lwline_construct(0, NULL, pa)); |
300 | 26.7k | } |
301 | 21 | else |
302 | 21 | { |
303 | 21 | lwmline_free(geom); |
304 | 21 | ptarray_free(pa); |
305 | 21 | return NULL; |
306 | 21 | } |
307 | 26.7k | } |
308 | 911 | return (LWGEOM *)geom; |
309 | 950 | } |
310 | | |
311 | | static inline LWGEOM * |
312 | | parse_geojson_multipolygon(json_object *geojson, int *hasz) |
313 | 1.24k | { |
314 | 1.24k | json_object *polys = parse_coordinates(geojson); |
315 | 1.24k | if (!polys) |
316 | 9 | return NULL; |
317 | 1.23k | LWGEOM *geom = (LWGEOM *)lwcollection_construct_empty(MULTIPOLYGONTYPE, 0, 1, 0); |
318 | 1.23k | int nPolys = json_object_array_length(polys); |
319 | | |
320 | 82.6k | for (int i = 0; i < nPolys; ++i) |
321 | 81.4k | { |
322 | 81.4k | json_object *rings = json_object_array_get_idx(polys, i); |
323 | 81.4k | LWPOLY *poly = parse_geojson_poly_rings(rings, hasz); |
324 | 81.4k | if (poly) |
325 | 67.7k | geom = (LWGEOM *)lwmpoly_add_lwpoly((LWMPOLY *)geom, poly); |
326 | 81.4k | } |
327 | | |
328 | 1.23k | return geom; |
329 | 1.24k | } |
330 | | |
331 | | static inline LWGEOM * |
332 | | parse_geojson_geometrycollection(json_object *geojson, int *hasz) |
333 | 2.29k | { |
334 | 2.29k | json_object *poObjGeoms = findMemberByName(geojson, "geometries"); |
335 | 2.29k | if (!poObjGeoms) |
336 | 95 | { |
337 | 95 | lwerror("Unable to find 'geometries' in GeoJSON string"); |
338 | 95 | return NULL; |
339 | 95 | } |
340 | 2.20k | LWGEOM *geom = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, 0, 1, 0); |
341 | | |
342 | 2.20k | if (json_type_array == json_object_get_type(poObjGeoms)) |
343 | 1.27k | { |
344 | 1.27k | const int nGeoms = json_object_array_length(poObjGeoms); |
345 | 13.4k | for (int i = 0; i < nGeoms; ++i) |
346 | 12.2k | { |
347 | 12.2k | json_object *poObjGeom = json_object_array_get_idx(poObjGeoms, i); |
348 | 12.2k | LWGEOM *t = parse_geojson(poObjGeom, hasz); |
349 | 12.2k | if (t) |
350 | 12.1k | geom = (LWGEOM *)lwcollection_add_lwgeom((LWCOLLECTION *)geom, t); |
351 | 110 | else |
352 | 110 | { |
353 | 110 | lwgeom_free(geom); |
354 | 110 | return NULL; |
355 | 110 | } |
356 | 12.2k | } |
357 | 1.27k | } |
358 | | |
359 | 2.09k | return geom; |
360 | 2.20k | } |
361 | | |
362 | | static inline LWGEOM * |
363 | | parse_geojson(json_object *geojson, int *hasz) |
364 | 14.5k | { |
365 | 14.5k | json_object *type = NULL; |
366 | 14.5k | const char *name; |
367 | | |
368 | 14.5k | if (!geojson) |
369 | 1 | { |
370 | 1 | lwerror("invalid GeoJSON representation"); |
371 | 1 | return NULL; |
372 | 1 | } |
373 | | |
374 | 14.5k | type = findMemberByName(geojson, "type"); |
375 | 14.5k | if (!type) |
376 | 312 | { |
377 | 312 | lwerror("unknown GeoJSON type"); |
378 | 312 | return NULL; |
379 | 312 | } |
380 | | |
381 | 14.2k | name = json_object_get_string(type); |
382 | | |
383 | 14.2k | if (strcasecmp(name, "Point") == 0) |
384 | 5.23k | return parse_geojson_point(geojson, hasz); |
385 | | |
386 | 8.98k | if (strcasecmp(name, "LineString") == 0) |
387 | 522 | return parse_geojson_linestring(geojson, hasz); |
388 | | |
389 | 8.45k | if (strcasecmp(name, "Polygon") == 0) |
390 | 646 | return parse_geojson_polygon(geojson, hasz); |
391 | | |
392 | 7.81k | if (strcasecmp(name, "MultiPoint") == 0) |
393 | 2.76k | return parse_geojson_multipoint(geojson, hasz); |
394 | | |
395 | 5.04k | if (strcasecmp(name, "MultiLineString") == 0) |
396 | 952 | return parse_geojson_multilinestring(geojson, hasz); |
397 | | |
398 | 4.09k | if (strcasecmp(name, "MultiPolygon") == 0) |
399 | 1.24k | return parse_geojson_multipolygon(geojson, hasz); |
400 | | |
401 | 2.85k | if (strcasecmp(name, "GeometryCollection") == 0) |
402 | 2.29k | return parse_geojson_geometrycollection(geojson, hasz); |
403 | | |
404 | 555 | lwerror("invalid GeoJson representation"); |
405 | 555 | return NULL; /* Never reach */ |
406 | 2.85k | } |
407 | | |
408 | | #endif /* HAVE_LIBJSON */ |
409 | | |
410 | | LWGEOM * |
411 | | lwgeom_from_geojson(const char *geojson, char **srs) |
412 | 2.47k | { |
413 | | #ifndef HAVE_LIBJSON |
414 | | *srs = NULL; |
415 | | lwerror("You need JSON-C for lwgeom_from_geojson"); |
416 | | return NULL; |
417 | | #else /* HAVE_LIBJSON */ |
418 | | |
419 | | /* Begin to Parse json */ |
420 | 2.47k | json_tokener *jstok = json_tokener_new(); |
421 | 2.47k | json_object *poObj = json_tokener_parse_ex(jstok, geojson, -1); |
422 | 2.47k | if (jstok->err != json_tokener_success) |
423 | 208 | { |
424 | 208 | char err[256]; |
425 | 208 | snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset); |
426 | 208 | json_tokener_free(jstok); |
427 | 208 | json_object_put(poObj); |
428 | 208 | lwerror("%s", err); |
429 | 208 | return NULL; |
430 | 208 | } |
431 | 2.26k | json_tokener_free(jstok); |
432 | | |
433 | 2.26k | *srs = NULL; |
434 | 2.26k | json_object *poObjSrs = findMemberByName(poObj, "crs"); |
435 | 2.26k | if (poObjSrs != NULL) |
436 | 193 | { |
437 | 193 | json_object *poObjSrsType = findMemberByName(poObjSrs, "type"); |
438 | 193 | if (poObjSrsType != NULL) |
439 | 145 | { |
440 | 145 | json_object *poObjSrsProps = findMemberByName(poObjSrs, "properties"); |
441 | 145 | if (poObjSrsProps) |
442 | 47 | { |
443 | 47 | json_object *poNameURL = findMemberByName(poObjSrsProps, "name"); |
444 | 47 | if (poNameURL) |
445 | 1 | { |
446 | 1 | const char *pszName = json_object_get_string(poNameURL); |
447 | 1 | if (pszName) |
448 | 1 | { |
449 | 1 | *srs = lwalloc(strlen(pszName) + 1); |
450 | 1 | strcpy(*srs, pszName); |
451 | 1 | } |
452 | 1 | } |
453 | 47 | } |
454 | 145 | } |
455 | 193 | } |
456 | | |
457 | 2.26k | int hasz = LW_FALSE; |
458 | 2.26k | LWGEOM *lwgeom = parse_geojson(poObj, &hasz); |
459 | 2.26k | json_object_put(poObj); |
460 | 2.26k | if (!lwgeom) |
461 | 1.23k | return NULL; |
462 | | |
463 | 1.03k | if (!hasz) |
464 | 873 | { |
465 | 873 | LWGEOM *tmp = lwgeom_force_2d(lwgeom); |
466 | 873 | lwgeom_free(lwgeom); |
467 | 873 | lwgeom = tmp; |
468 | 873 | } |
469 | 1.03k | lwgeom_add_bbox(lwgeom); |
470 | 1.03k | return lwgeom; |
471 | 2.26k | #endif /* HAVE_LIBJSON */ |
472 | 2.26k | } |