Coverage Report

Created: 2026-03-31 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}