PostGIS  3.4.0dev-r@@SVN_REVISION@@
lwin_geojson.c
Go to the documentation of this file.
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 {
54  json_object *poTmp;
55  json_object_iter it;
56 
57  poTmp = poObj;
58 
59  if (!pszName || !poObj)
60  return NULL;
61 
62  it.key = NULL;
63  it.val = NULL;
64  it.entry = NULL;
65 
66  if (json_object_get_object(poTmp))
67  {
68  if (!json_object_get_object(poTmp)->head)
69  {
70  lwerror("invalid GeoJSON representation");
71  return NULL;
72  }
73 
74  for (it.entry = json_object_get_object(poTmp)->head;
75  (it.entry ? (it.key = (char *)it.entry->k, it.val = (json_object *)it.entry->v, it.entry) : 0);
76  it.entry = it.entry->next)
77  {
78  if (strcasecmp((char *)it.key, pszName) == 0)
79  return it.val;
80  }
81  }
82 
83  return NULL;
84 }
85 
86 static inline json_object *
87 parse_coordinates(json_object *geojson)
88 {
89  json_object *coordinates = findMemberByName(geojson, "coordinates");
90  if (!coordinates)
91  {
92  lwerror("Unable to find 'coordinates' in GeoJSON string");
93  return NULL;
94  }
95 
96  if (json_type_array != json_object_get_type(coordinates))
97  {
98  lwerror("The 'coordinates' in GeoJSON are not an array");
99  return NULL;
100  }
101  return coordinates;
102 }
103 
104 
105 static inline int
106 parse_geojson_coord(json_object *poObj, int *hasz, POINTARRAY *pa)
107 {
108  POINT4D pt = {0, 0, 0, 0};
109 
110  if (json_object_get_type(poObj) == json_type_array)
111  {
112  json_object *poObjCoord = NULL;
113  const int nSize = json_object_array_length(poObj);
114  if (nSize == 0)
115  return LW_TRUE;
116  if (nSize < 2)
117  {
118  lwerror("Too few ordinates in GeoJSON");
119  return LW_FAILURE;
120  }
121 
122  /* Read X coordinate */
123  poObjCoord = json_object_array_get_idx(poObj, 0);
124  pt.x = json_object_get_double(poObjCoord);
125 
126  /* Read Y coordinate */
127  poObjCoord = json_object_array_get_idx(poObj, 1);
128  pt.y = json_object_get_double(poObjCoord);
129 
130  if (nSize > 2) /* should this be >= 3 ? */
131  {
132  /* Read Z coordinate */
133  poObjCoord = json_object_array_get_idx(poObj, 2);
134  pt.z = json_object_get_double(poObjCoord);
135  *hasz = LW_TRUE;
136  }
137  }
138  else
139  {
140  /* If it's not an array, just don't handle it */
141  lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
142  return LW_FAILURE;
143  }
144 
145  return ptarray_append_point(pa, &pt, LW_TRUE);
146 }
147 
148 static inline LWGEOM *
149 parse_geojson_point(json_object *geojson, int *hasz)
150 {
151  json_object *coords = parse_coordinates(geojson);
152  if (!coords)
153  return NULL;
154  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
155  parse_geojson_coord(coords, hasz, pa);
156  return (LWGEOM *)lwpoint_construct(0, NULL, pa);
157 }
158 
159 static inline LWGEOM *
160 parse_geojson_linestring(json_object *geojson, int *hasz)
161 {
162  json_object *points = parse_coordinates(geojson);
163  if (!points)
164  return NULL;
165  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
166  const int nPoints = json_object_array_length(points);
167  for (int i = 0; i < nPoints; i++)
168  {
169  json_object *coords = json_object_array_get_idx(points, i);
170  parse_geojson_coord(coords, hasz, pa);
171  }
172  return (LWGEOM *)lwline_construct(0, NULL, pa);
173 }
174 
175 static inline LWPOLY *
176 parse_geojson_poly_rings(json_object *rings, int *hasz)
177 {
178  if (!rings || json_object_get_type(rings) != json_type_array)
179  return NULL;
180 
181  int nRings = json_object_array_length(rings);
182 
183  /* No rings => POLYGON EMPTY */
184  if (!nRings)
185  return lwpoly_construct_empty(0, 1, 0);
186 
187  /* Expecting up to nRings otherwise */
188  POINTARRAY **ppa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nRings);
189  int o = 0;
190 
191  for (int i = 0; i < nRings; i++)
192  {
193  json_object *points = json_object_array_get_idx(rings, i);
194  if (!points || json_object_get_type(points) != json_type_array)
195  {
196  for (int k = 0; k < o; k++)
197  ptarray_free(ppa[k]);
198  lwfree(ppa);
199  lwerror("The 'coordinates' in GeoJSON ring are not an array");
200  return NULL;
201  }
202  int nPoints = json_object_array_length(points);
203 
204  /* Skip empty rings */
205  if (!nPoints)
206  {
207  /* Empty outer? Don't promote first hole to outer, holes don't matter. */
208  if (!i)
209  break;
210  else
211  continue;
212  }
213 
214  ppa[o] = ptarray_construct_empty(1, 0, 1);
215  for (int j = 0; j < nPoints; j++)
216  {
217  json_object *coords = NULL;
218  coords = json_object_array_get_idx(points, j);
219  if (LW_FAILURE == parse_geojson_coord(coords, hasz, ppa[o]))
220  {
221  for (int k = 0; k <= o; k++)
222  ptarray_free(ppa[k]);
223  lwfree(ppa);
224  lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
225  return NULL;
226  }
227  }
228  o++;
229  }
230 
231  /* All the rings were empty! */
232  if (!o)
233  {
234  lwfree(ppa);
235  return lwpoly_construct_empty(0, 1, 0);
236  }
237 
238  return lwpoly_construct(0, NULL, o, ppa);
239 }
240 
241 static inline LWGEOM *
242 parse_geojson_polygon(json_object *geojson, int *hasz)
243 {
244  return (LWGEOM *)parse_geojson_poly_rings(parse_coordinates(geojson), hasz);
245 }
246 
247 static inline LWGEOM *
248 parse_geojson_multipoint(json_object *geojson, int *hasz)
249 {
250  json_object *points = parse_coordinates(geojson);
251  if (!points)
252  return NULL;
254 
255  const int nPoints = json_object_array_length(points);
256  for (int i = 0; i < nPoints; ++i)
257  {
258  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
259  json_object *coord = json_object_array_get_idx(points, i);
260  if (parse_geojson_coord(coord, hasz, pa))
261  geom = lwmpoint_add_lwpoint(geom, lwpoint_construct(0, NULL, pa));
262  else
263  {
264  lwmpoint_free(geom);
265  ptarray_free(pa);
266  return NULL;
267  }
268  }
269 
270  return (LWGEOM *)geom;
271 }
272 
273 static inline LWGEOM *
274 parse_geojson_multilinestring(json_object *geojson, int *hasz)
275 {
276  json_object *mls = parse_coordinates(geojson);
277  if (!mls)
278  return NULL;
280  const int nLines = json_object_array_length(mls);
281  for (int i = 0; i < nLines; ++i)
282  {
283  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
284  json_object *coords = json_object_array_get_idx(mls, i);
285 
286  if (json_type_array == json_object_get_type(coords))
287  {
288  const int nPoints = json_object_array_length(coords);
289  for (int j = 0; j < nPoints; ++j)
290  {
291  json_object *coord = json_object_array_get_idx(coords, j);
292  if (!parse_geojson_coord(coord, hasz, pa))
293  {
294  lwmline_free(geom);
295  ptarray_free(pa);
296  return NULL;
297  }
298  }
299  geom = lwmline_add_lwline(geom, lwline_construct(0, NULL, pa));
300  }
301  else
302  {
303  lwmline_free(geom);
304  ptarray_free(pa);
305  return NULL;
306  }
307  }
308  return (LWGEOM *)geom;
309 }
310 
311 static inline LWGEOM *
312 parse_geojson_multipolygon(json_object *geojson, int *hasz)
313 {
314  json_object *polys = parse_coordinates(geojson);
315  if (!polys)
316  return NULL;
318  int nPolys = json_object_array_length(polys);
319 
320  for (int i = 0; i < nPolys; ++i)
321  {
322  json_object *rings = json_object_array_get_idx(polys, i);
323  LWPOLY *poly = parse_geojson_poly_rings(rings, hasz);
324  if (poly)
325  geom = (LWGEOM *)lwmpoly_add_lwpoly((LWMPOLY *)geom, poly);
326  }
327 
328  return geom;
329 }
330 
331 static inline LWGEOM *
332 parse_geojson_geometrycollection(json_object *geojson, int *hasz)
333 {
334  json_object *poObjGeoms = findMemberByName(geojson, "geometries");
335  if (!poObjGeoms)
336  {
337  lwerror("Unable to find 'geometries' in GeoJSON string");
338  return NULL;
339  }
341 
342  if (json_type_array == json_object_get_type(poObjGeoms))
343  {
344  const int nGeoms = json_object_array_length(poObjGeoms);
345  for (int i = 0; i < nGeoms; ++i)
346  {
347  json_object *poObjGeom = json_object_array_get_idx(poObjGeoms, i);
348  LWGEOM *t = parse_geojson(poObjGeom, hasz);
349  if (t)
350  geom = (LWGEOM *)lwcollection_add_lwgeom((LWCOLLECTION *)geom, t);
351  else
352  {
353  lwgeom_free(geom);
354  return NULL;
355  }
356  }
357  }
358 
359  return geom;
360 }
361 
362 static inline LWGEOM *
363 parse_geojson(json_object *geojson, int *hasz)
364 {
365  json_object *type = NULL;
366  const char *name;
367 
368  if (!geojson)
369  {
370  lwerror("invalid GeoJSON representation");
371  return NULL;
372  }
373 
374  type = findMemberByName(geojson, "type");
375  if (!type)
376  {
377  lwerror("unknown GeoJSON type");
378  return NULL;
379  }
380 
381  name = json_object_get_string(type);
382 
383  if (strcasecmp(name, "Point") == 0)
384  return parse_geojson_point(geojson, hasz);
385 
386  if (strcasecmp(name, "LineString") == 0)
387  return parse_geojson_linestring(geojson, hasz);
388 
389  if (strcasecmp(name, "Polygon") == 0)
390  return parse_geojson_polygon(geojson, hasz);
391 
392  if (strcasecmp(name, "MultiPoint") == 0)
393  return parse_geojson_multipoint(geojson, hasz);
394 
395  if (strcasecmp(name, "MultiLineString") == 0)
396  return parse_geojson_multilinestring(geojson, hasz);
397 
398  if (strcasecmp(name, "MultiPolygon") == 0)
399  return parse_geojson_multipolygon(geojson, hasz);
400 
401  if (strcasecmp(name, "GeometryCollection") == 0)
402  return parse_geojson_geometrycollection(geojson, hasz);
403 
404  lwerror("invalid GeoJson representation");
405  return NULL; /* Never reach */
406 }
407 
408 #endif /* HAVE_LIBJSON */
409 
410 LWGEOM *
411 lwgeom_from_geojson(const char *geojson, char **srs)
412 {
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  json_tokener *jstok = json_tokener_new();
421  json_object *poObj = json_tokener_parse_ex(jstok, geojson, -1);
422  if (jstok->err != json_tokener_success)
423  {
424  char err[256];
425  snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset);
426  json_tokener_free(jstok);
427  json_object_put(poObj);
428  lwerror(err);
429  return NULL;
430  }
431  json_tokener_free(jstok);
432 
433  *srs = NULL;
434  json_object *poObjSrs = findMemberByName(poObj, "crs");
435  if (poObjSrs != NULL)
436  {
437  json_object *poObjSrsType = findMemberByName(poObjSrs, "type");
438  if (poObjSrsType != NULL)
439  {
440  json_object *poObjSrsProps = findMemberByName(poObjSrs, "properties");
441  if (poObjSrsProps)
442  {
443  json_object *poNameURL = findMemberByName(poObjSrsProps, "name");
444  if (poNameURL)
445  {
446  const char *pszName = json_object_get_string(poNameURL);
447  if (pszName)
448  {
449  *srs = lwalloc(strlen(pszName) + 1);
450  strcpy(*srs, pszName);
451  }
452  }
453  }
454  }
455  }
456 
457  int hasz = LW_FALSE;
458  LWGEOM *lwgeom = parse_geojson(poObj, &hasz);
459  json_object_put(poObj);
460  if (!lwgeom)
461  return NULL;
462 
463  if (!hasz)
464  {
465  LWGEOM *tmp = lwgeom_force_2d(lwgeom);
466  lwgeom_free(lwgeom);
467  lwgeom = tmp;
468  }
469  lwgeom_add_bbox(lwgeom);
470  return lwgeom;
471 #endif /* HAVE_LIBJSON */
472 }
#define LW_FALSE
Definition: liblwgeom.h:94
#define COLLECTIONTYPE
Definition: liblwgeom.h:108
void lwmpoint_free(LWMPOINT *mpt)
Definition: lwmpoint.c:72
#define LW_FAILURE
Definition: liblwgeom.h:96
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1155
#define MULTILINETYPE
Definition: liblwgeom.h:106
#define MULTIPOINTTYPE
Definition: liblwgeom.h:105
LWMLINE * lwmline_add_lwline(LWMLINE *mobj, const LWLINE *obj)
Definition: lwmline.c:46
LWLINE * lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition: lwline.c:42
LWMPOINT * lwmpoint_add_lwpoint(LWMPOINT *mobj, const LWPOINT *obj)
Definition: lwmpoint.c:45
LWMPOLY * lwmpoly_add_lwpoly(LWMPOLY *mobj, const LWPOLY *obj)
Definition: lwmpoly.c:47
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:107
void lwfree(void *mem)
Definition: lwutil.c:242
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition: lwpoint.c:129
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition: ptarray.c:59
LWCOLLECTION * lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm)
Definition: lwcollection.c:92
void ptarray_free(POINTARRAY *pa)
Definition: ptarray.c:319
int ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int allow_duplicates)
Append a point to the end of an existing POINTARRAY If allow_duplicate is LW_FALSE,...
Definition: ptarray.c:147
LWCOLLECTION * lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom)
Appends geom to the collection managed by col.
Definition: lwcollection.c:188
void * lwalloc(size_t size)
Definition: lwutil.c:227
void lwmline_free(LWMLINE *mline)
Definition: lwmline.c:112
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:93
LWPOLY * lwpoly_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwpoly.c:161
LWPOLY * lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
Definition: lwpoly.c:43
void lwgeom_add_bbox(LWGEOM *lwgeom)
Compute a bbox if not already computed.
Definition: lwgeom.c:695
LWGEOM * lwgeom_force_2d(const LWGEOM *geom)
Strip out the Z/M components of an LWGEOM.
Definition: lwgeom.c:793
This library is the generic geometry handling section of PostGIS.
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
LWGEOM * lwgeom_from_geojson(const char *geojson, char **srs)
Create an LWGEOM object from a GeoJSON representation.
Definition: lwin_geojson.c:411
static LWGEOM * parse_geojson_geometrycollection(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:332
#define json_tokener_error_desc(x)
Definition: lwin_geojson.c:43
static LWGEOM * parse_geojson(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:363
static json_object * parse_coordinates(json_object *geojson)
Definition: lwin_geojson.c:87
static LWGEOM * parse_geojson_multipoint(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:248
static LWGEOM * parse_geojson_multipolygon(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:312
static LWGEOM * parse_geojson_multilinestring(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:274
static int parse_geojson_coord(json_object *poObj, int *hasz, POINTARRAY *pa)
Definition: lwin_geojson.c:106
static LWPOLY * parse_geojson_poly_rings(json_object *rings, int *hasz)
Definition: lwin_geojson.c:176
static LWGEOM * parse_geojson_linestring(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:160
static LWGEOM * parse_geojson_polygon(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:242
static LWGEOM * parse_geojson_point(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:149
static json_object * findMemberByName(json_object *poObj, const char *pszName)
Definition: lwin_geojson.c:52
type
Definition: ovdump.py:42
double x
Definition: liblwgeom.h:414
double z
Definition: liblwgeom.h:414
double y
Definition: liblwgeom.h:414