PostGIS  3.0.6dev-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 < 2)
115  {
116  lwerror("Too few ordinates in GeoJSON");
117  return LW_FAILURE;
118  }
119 
120  /* Read X coordinate */
121  poObjCoord = json_object_array_get_idx(poObj, 0);
122  pt.x = json_object_get_double(poObjCoord);
123 
124  /* Read Y coordinate */
125  poObjCoord = json_object_array_get_idx(poObj, 1);
126  pt.y = json_object_get_double(poObjCoord);
127 
128  if (nSize > 2) /* should this be >= 3 ? */
129  {
130  /* Read Z coordinate */
131  poObjCoord = json_object_array_get_idx(poObj, 2);
132  pt.z = json_object_get_double(poObjCoord);
133  *hasz = LW_TRUE;
134  }
135  }
136  else
137  {
138  /* If it's not an array, just don't handle it */
139  lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
140  return LW_FAILURE;
141  }
142 
143  return ptarray_append_point(pa, &pt, LW_TRUE);
144 }
145 
146 static inline LWGEOM *
147 parse_geojson_point(json_object *geojson, int *hasz)
148 {
149  json_object *coords = parse_coordinates(geojson);
150  if (!coords)
151  return NULL;
152  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
153  parse_geojson_coord(coords, hasz, pa);
154  return (LWGEOM *)lwpoint_construct(0, NULL, pa);
155 }
156 
157 static inline LWGEOM *
158 parse_geojson_linestring(json_object *geojson, int *hasz)
159 {
160  json_object *points = parse_coordinates(geojson);
161  if (!points)
162  return NULL;
163  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
164  const int nPoints = json_object_array_length(points);
165  for (int i = 0; i < nPoints; i++)
166  {
167  json_object *coords = json_object_array_get_idx(points, i);
168  parse_geojson_coord(coords, hasz, pa);
169  }
170  return (LWGEOM *)lwline_construct(0, NULL, pa);
171 }
172 
173 static inline LWPOLY *
174 parse_geojson_poly_rings(json_object *rings, int *hasz)
175 {
176  if (!rings || json_object_get_type(rings) != json_type_array)
177  return NULL;
178 
179  int nRings = json_object_array_length(rings);
180 
181  /* No rings => POLYGON EMPTY */
182  if (!nRings)
183  return lwpoly_construct_empty(0, 1, 0);
184 
185  /* Expecting up to nRings otherwise */
186  POINTARRAY **ppa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nRings);
187  int o = 0;
188 
189  for (int i = 0; i < nRings; i++)
190  {
191  json_object *points = json_object_array_get_idx(rings, i);
192  if (!points || json_object_get_type(points) != json_type_array)
193  {
194  for (int k = 0; k < o; k++)
195  ptarray_free(ppa[k]);
196  lwfree(ppa);
197  lwerror("The 'coordinates' in GeoJSON ring are not an array");
198  return NULL;
199  }
200  int nPoints = json_object_array_length(points);
201 
202  /* Skip empty rings */
203  if (!nPoints)
204  {
205  /* Empty outer? Don't promote first hole to outer, holes don't matter. */
206  if (!i)
207  break;
208  else
209  continue;
210  }
211 
212  ppa[o] = ptarray_construct_empty(1, 0, 1);
213  for (int j = 0; j < nPoints; j++)
214  {
215  json_object *coords = NULL;
216  coords = json_object_array_get_idx(points, j);
217  if (LW_FAILURE == parse_geojson_coord(coords, hasz, ppa[o]))
218  {
219  for (int k = 0; k <= o; k++)
220  ptarray_free(ppa[k]);
221  lwfree(ppa);
222  lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
223  return NULL;
224  }
225  }
226  o++;
227  }
228 
229  /* All the rings were empty! */
230  if (!o)
231  {
232  lwfree(ppa);
233  return lwpoly_construct_empty(0, 1, 0);
234  }
235 
236  return lwpoly_construct(0, NULL, o, ppa);
237 }
238 
239 static inline LWGEOM *
240 parse_geojson_polygon(json_object *geojson, int *hasz)
241 {
242  return (LWGEOM *)parse_geojson_poly_rings(parse_coordinates(geojson), hasz);
243 }
244 
245 static inline LWGEOM *
246 parse_geojson_multipoint(json_object *geojson, int *hasz)
247 {
248  json_object *points = parse_coordinates(geojson);
249  if (!points)
250  return NULL;
252 
253  const int nPoints = json_object_array_length(points);
254  for (int i = 0; i < nPoints; ++i)
255  {
256  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
257  json_object *coord = json_object_array_get_idx(points, i);
258  if (parse_geojson_coord(coord, hasz, pa))
259  geom = lwmpoint_add_lwpoint(geom, lwpoint_construct(0, NULL, pa));
260  else
261  {
262  lwmpoint_free(geom);
263  ptarray_free(pa);
264  return NULL;
265  }
266  }
267 
268  return (LWGEOM *)geom;
269 }
270 
271 static inline LWGEOM *
272 parse_geojson_multilinestring(json_object *geojson, int *hasz)
273 {
274  json_object *mls = parse_coordinates(geojson);
275  if (!mls)
276  return NULL;
278  const int nLines = json_object_array_length(mls);
279  for (int i = 0; i < nLines; ++i)
280  {
281  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
282  json_object *coords = json_object_array_get_idx(mls, i);
283 
284  if (json_type_array == json_object_get_type(coords))
285  {
286  const int nPoints = json_object_array_length(coords);
287  for (int j = 0; j < nPoints; ++j)
288  {
289  json_object *coord = json_object_array_get_idx(coords, j);
290  if (!parse_geojson_coord(coord, hasz, pa))
291  {
292  lwmline_free(geom);
293  ptarray_free(pa);
294  return NULL;
295  }
296  }
297  geom = lwmline_add_lwline(geom, lwline_construct(0, NULL, pa));
298  }
299  else
300  {
301  lwmline_free(geom);
302  ptarray_free(pa);
303  return NULL;
304  }
305  }
306  return (LWGEOM *)geom;
307 }
308 
309 static inline LWGEOM *
310 parse_geojson_multipolygon(json_object *geojson, int *hasz)
311 {
312  json_object *polys = parse_coordinates(geojson);
313  if (!polys)
314  return NULL;
316  int nPolys = json_object_array_length(polys);
317 
318  for (int i = 0; i < nPolys; ++i)
319  {
320  json_object *rings = json_object_array_get_idx(polys, i);
321  LWPOLY *poly = parse_geojson_poly_rings(rings, hasz);
322  if (poly)
323  geom = (LWGEOM *)lwmpoly_add_lwpoly((LWMPOLY *)geom, poly);
324  }
325 
326  return geom;
327 }
328 
329 static inline LWGEOM *
330 parse_geojson_geometrycollection(json_object *geojson, int *hasz)
331 {
332  json_object *poObjGeoms = findMemberByName(geojson, "geometries");
333  if (!poObjGeoms)
334  {
335  lwerror("Unable to find 'geometries' in GeoJSON string");
336  return NULL;
337  }
339 
340  if (json_type_array == json_object_get_type(poObjGeoms))
341  {
342  const int nGeoms = json_object_array_length(poObjGeoms);
343  for (int i = 0; i < nGeoms; ++i)
344  {
345  json_object *poObjGeom = json_object_array_get_idx(poObjGeoms, i);
346  LWGEOM *t = parse_geojson(poObjGeom, hasz);
347  if (t)
348  geom = (LWGEOM *)lwcollection_add_lwgeom((LWCOLLECTION *)geom, t);
349  else
350  {
351  lwgeom_free(geom);
352  return NULL;
353  }
354  }
355  }
356 
357  return geom;
358 }
359 
360 static inline LWGEOM *
361 parse_geojson(json_object *geojson, int *hasz)
362 {
363  json_object *type = NULL;
364  const char *name;
365 
366  if (!geojson)
367  {
368  lwerror("invalid GeoJSON representation");
369  return NULL;
370  }
371 
372  type = findMemberByName(geojson, "type");
373  if (!type)
374  {
375  lwerror("unknown GeoJSON type");
376  return NULL;
377  }
378 
379  name = json_object_get_string(type);
380 
381  if (strcasecmp(name, "Point") == 0)
382  return parse_geojson_point(geojson, hasz);
383 
384  if (strcasecmp(name, "LineString") == 0)
385  return parse_geojson_linestring(geojson, hasz);
386 
387  if (strcasecmp(name, "Polygon") == 0)
388  return parse_geojson_polygon(geojson, hasz);
389 
390  if (strcasecmp(name, "MultiPoint") == 0)
391  return parse_geojson_multipoint(geojson, hasz);
392 
393  if (strcasecmp(name, "MultiLineString") == 0)
394  return parse_geojson_multilinestring(geojson, hasz);
395 
396  if (strcasecmp(name, "MultiPolygon") == 0)
397  return parse_geojson_multipolygon(geojson, hasz);
398 
399  if (strcasecmp(name, "GeometryCollection") == 0)
400  return parse_geojson_geometrycollection(geojson, hasz);
401 
402  lwerror("invalid GeoJson representation");
403  return NULL; /* Never reach */
404 }
405 
406 #endif /* HAVE_LIBJSON */
407 
408 LWGEOM *
409 lwgeom_from_geojson(const char *geojson, char **srs)
410 {
411 #ifndef HAVE_LIBJSON
412  *srs = NULL;
413  lwerror("You need JSON-C for lwgeom_from_geojson");
414  return NULL;
415 #else /* HAVE_LIBJSON */
416 
417  /* Begin to Parse json */
418  json_tokener *jstok = json_tokener_new();
419  json_object *poObj = json_tokener_parse_ex(jstok, geojson, -1);
420  if (jstok->err != json_tokener_success)
421  {
422  char err[256];
423  snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset);
424  json_tokener_free(jstok);
425  json_object_put(poObj);
426  lwerror(err);
427  return NULL;
428  }
429  json_tokener_free(jstok);
430 
431  *srs = NULL;
432  json_object *poObjSrs = findMemberByName(poObj, "crs");
433  if (poObjSrs != NULL)
434  {
435  json_object *poObjSrsType = findMemberByName(poObjSrs, "type");
436  if (poObjSrsType != NULL)
437  {
438  json_object *poObjSrsProps = findMemberByName(poObjSrs, "properties");
439  if (poObjSrsProps)
440  {
441  json_object *poNameURL = findMemberByName(poObjSrsProps, "name");
442  if (poNameURL)
443  {
444  const char *pszName = json_object_get_string(poNameURL);
445  if (pszName)
446  {
447  *srs = lwalloc(strlen(pszName) + 1);
448  strcpy(*srs, pszName);
449  }
450  }
451  }
452  }
453  }
454 
455  int hasz = LW_FALSE;
456  LWGEOM *lwgeom = parse_geojson(poObj, &hasz);
457  json_object_put(poObj);
458  if (!lwgeom)
459  return NULL;
460 
461  if (!hasz)
462  {
463  LWGEOM *tmp = lwgeom_force_2d(lwgeom);
464  lwgeom_free(lwgeom);
465  lwgeom = tmp;
466  }
467  lwgeom_add_bbox(lwgeom);
468  return lwgeom;
469 #endif /* HAVE_LIBJSON */
470 }
#define LW_FALSE
Definition: liblwgeom.h:108
#define COLLECTIONTYPE
Definition: liblwgeom.h:122
void lwmpoint_free(LWMPOINT *mpt)
Definition: lwmpoint.c:72
#define LW_FAILURE
Definition: liblwgeom.h:110
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1138
#define MULTILINETYPE
Definition: liblwgeom.h:120
#define MULTIPOINTTYPE
Definition: liblwgeom.h:119
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:121
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:107
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:677
LWGEOM * lwgeom_force_2d(const LWGEOM *geom)
Strip out the Z/M components of an LWGEOM.
Definition: lwgeom.c:775
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:409
static LWGEOM * parse_geojson_geometrycollection(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:330
#define json_tokener_error_desc(x)
Definition: lwin_geojson.c:43
static LWGEOM * parse_geojson(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:361
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:246
static LWGEOM * parse_geojson_multipolygon(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:310
static LWGEOM * parse_geojson_multilinestring(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:272
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:174
static LWGEOM * parse_geojson_linestring(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:158
static LWGEOM * parse_geojson_polygon(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:240
static LWGEOM * parse_geojson_point(json_object *geojson, int *hasz)
Definition: lwin_geojson.c:147
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:400
double z
Definition: liblwgeom.h:400
double y
Definition: liblwgeom.h:400