Coverage Report

Created: 2026-09-03 06:43

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
31.9k
{
54
31.9k
  json_object *poTmp;
55
31.9k
  json_object_iter it;
56
57
31.9k
  poTmp = poObj;
58
59
31.9k
  if (!pszName || !poObj)
60
1
    return NULL;
61
62
31.9k
  it.key = NULL;
63
31.9k
  it.val = NULL;
64
31.9k
  it.entry = NULL;
65
66
31.9k
  if (json_object_get_object(poTmp))
67
31.9k
  {
68
31.9k
    if (!json_object_get_object(poTmp)->head)
69
8
    {
70
8
      lwerror("invalid GeoJSON representation");
71
8
      return NULL;
72
8
    }
73
74
31.9k
    for (it.entry = json_object_get_object(poTmp)->head;
75
52.6k
         (it.entry ? (it.key = (char *)it.entry->k, it.val = (json_object *)it.entry->v, it.entry) : 0);
76
31.9k
         it.entry = it.entry->next)
77
49.7k
    {
78
49.7k
      if (strcasecmp((char *)it.key, pszName) == 0)
79
29.0k
        return it.val;
80
49.7k
    }
81
31.9k
  }
82
83
2.86k
  return NULL;
84
31.9k
}
85
86
static inline json_object *
87
parse_coordinates(json_object *geojson)
88
11.8k
{
89
11.8k
  json_object *coordinates = findMemberByName(geojson, "coordinates");
90
11.8k
  if (!coordinates)
91
132
  {
92
132
    lwerror("Unable to find 'coordinates' in GeoJSON string");
93
132
    return NULL;
94
132
  }
95
96
11.6k
  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.6k
  return coordinates;
102
11.6k
}
103
104
105
static inline int
106
parse_geojson_coord(json_object *poObj, int *hasz, POINTARRAY *pa)
107
683k
{
108
683k
  POINT4D pt = {0, 0, 0, 0};
109
110
683k
  if (json_object_get_type(poObj) == json_type_array)
111
617k
  {
112
617k
    json_object *poObjCoord = NULL;
113
617k
    const int nSize = json_object_array_length(poObj);
114
617k
    if (nSize == 0)
115
445k
      return LW_TRUE;
116
171k
    if (nSize < 2)
117
217
    {
118
217
      lwerror("Too few ordinates in GeoJSON");
119
217
      return LW_FAILURE;
120
217
    }
121
122
    /* Read X coordinate */
123
171k
    poObjCoord = json_object_array_get_idx(poObj, 0);
124
171k
    pt.x = json_object_get_double(poObjCoord);
125
126
    /* Read Y coordinate */
127
171k
    poObjCoord = json_object_array_get_idx(poObj, 1);
128
171k
    pt.y = json_object_get_double(poObjCoord);
129
130
171k
    if (nSize > 2) /* should this be >= 3 ? */
131
700
    {
132
      /* Read Z coordinate */
133
700
      poObjCoord = json_object_array_get_idx(poObj, 2);
134
700
      pt.z = json_object_get_double(poObjCoord);
135
700
      *hasz = LW_TRUE;
136
700
    }
137
171k
  }
138
66.3k
  else
139
66.3k
  {
140
    /* If it's not an array, just don't handle it */
141
66.3k
    lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
142
66.3k
    return LW_FAILURE;
143
66.3k
  }
144
145
171k
  return ptarray_append_point(pa, &pt, LW_TRUE);
146
683k
}
147
148
static inline LWGEOM *
149
parse_geojson_point(json_object *geojson, int *hasz)
150
5.36k
{
151
5.36k
  json_object *coords = parse_coordinates(geojson);
152
5.36k
  if (!coords)
153
120
    return NULL;
154
5.24k
  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
155
5.24k
  parse_geojson_coord(coords, hasz, pa);
156
5.24k
  return (LWGEOM *)lwpoint_construct(0, NULL, pa);
157
5.36k
}
158
159
static inline LWGEOM *
160
parse_geojson_linestring(json_object *geojson, int *hasz)
161
536
{
162
536
  json_object *points = parse_coordinates(geojson);
163
536
  if (!points)
164
2
    return NULL;
165
534
  POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
166
534
  const int nPoints = json_object_array_length(points);
167
99.6k
  for (int i = 0; i < nPoints; i++)
168
99.0k
  {
169
99.0k
    json_object *coords = json_object_array_get_idx(points, i);
170
99.0k
    parse_geojson_coord(coords, hasz, pa);
171
99.0k
  }
172
534
  return (LWGEOM *)lwline_construct(0, NULL, pa);
173
536
}
174
175
static inline LWPOLY *
176
parse_geojson_poly_rings(json_object *rings, int *hasz)
177
135k
{
178
135k
  if (!rings || json_object_get_type(rings) != json_type_array)
179
33.4k
    return NULL;
180
181
102k
  int nRings = json_object_array_length(rings);
182
183
  /* No rings => POLYGON EMPTY */
184
102k
  if (!nRings)
185
99.4k
    return lwpoly_construct_empty(0, 1, 0);
186
187
  /* Expecting up to nRings otherwise */
188
2.53k
  POINTARRAY **ppa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nRings);
189
2.53k
  int o = 0;
190
191
275k
  for (int i = 0; i < nRings; i++)
192
274k
  {
193
274k
    json_object *points = json_object_array_get_idx(rings, i);
194
274k
    if (!points || json_object_get_type(points) != json_type_array)
195
679
    {
196
17.4k
      for (int k = 0; k < o; k++)
197
16.7k
        ptarray_free(ppa[k]);
198
679
      lwfree(ppa);
199
679
      lwerror("The 'coordinates' in GeoJSON ring are not an array");
200
679
      return NULL;
201
679
    }
202
273k
    int nPoints = json_object_array_length(points);
203
204
    /* Skip empty rings */
205
273k
    if (!nPoints)
206
98.7k
    {
207
      /* Empty outer? Don't promote first hole to outer, holes don't matter. */
208
98.7k
      if (!i)
209
201
        break;
210
98.5k
      else
211
98.5k
        continue;
212
98.7k
    }
213
214
175k
    ppa[o] = ptarray_construct_empty(1, 0, 1);
215
516k
    for (int j = 0; j < nPoints; j++)
216
341k
    {
217
341k
      json_object *coords = NULL;
218
341k
      coords = json_object_array_get_idx(points, j);
219
341k
      if (LW_FAILURE == parse_geojson_coord(coords, hasz, ppa[o]))
220
258
      {
221
1.21k
        for (int k = 0; k <= o; k++)
222
956
          ptarray_free(ppa[k]);
223
258
        lwfree(ppa);
224
258
        lwerror("The 'coordinates' in GeoJSON are not sufficiently nested");
225
258
        return NULL;
226
258
      }
227
341k
    }
228
174k
    o++;
229
174k
  }
230
231
  /* All the rings were empty! */
232
1.60k
  if (!o)
233
201
  {
234
201
    lwfree(ppa);
235
201
    return lwpoly_construct_empty(0, 1, 0);
236
201
  }
237
238
1.39k
  return lwpoly_construct(0, NULL, o, ppa);
239
1.60k
}
240
241
static inline LWGEOM *
242
parse_geojson_polygon(json_object *geojson, int *hasz)
243
662
{
244
662
  return (LWGEOM *)parse_geojson_poly_rings(parse_coordinates(geojson), hasz);
245
662
}
246
247
static inline LWGEOM *
248
parse_geojson_multipoint(json_object *geojson, int *hasz)
249
3.01k
{
250
3.01k
  json_object *points = parse_coordinates(geojson);
251
3.01k
  if (!points)
252
1
    return NULL;
253
3.01k
  LWMPOINT *geom = (LWMPOINT *)lwcollection_construct_empty(MULTIPOINTTYPE, 0, 1, 0);
254
255
3.01k
  const int nPoints = json_object_array_length(points);
256
238k
  for (int i = 0; i < nPoints; ++i)
257
235k
  {
258
235k
    POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
259
235k
    json_object *coord = json_object_array_get_idx(points, i);
260
235k
    if (parse_geojson_coord(coord, hasz, pa))
261
235k
      geom = lwmpoint_add_lwpoint(geom, lwpoint_construct(0, NULL, pa));
262
27
    else
263
27
    {
264
27
      lwmpoint_free(geom);
265
27
      ptarray_free(pa);
266
27
      return NULL;
267
27
    }
268
235k
  }
269
270
2.99k
  return (LWGEOM *)geom;
271
3.01k
}
272
273
static inline LWGEOM *
274
parse_geojson_multilinestring(json_object *geojson, int *hasz)
275
999
{
276
999
  json_object *mls = parse_coordinates(geojson);
277
999
  if (!mls)
278
1
    return NULL;
279
998
  LWMLINE *geom = (LWMLINE *)lwcollection_construct_empty(MULTILINETYPE, 0, 1, 0);
280
998
  const int nLines = json_object_array_length(mls);
281
68.7k
  for (int i = 0; i < nLines; ++i)
282
67.7k
  {
283
67.7k
    POINTARRAY *pa = ptarray_construct_empty(1, 0, 1);
284
67.7k
    json_object *coords = json_object_array_get_idx(mls, i);
285
286
67.7k
    if (json_type_array == json_object_get_type(coords))
287
67.7k
    {
288
67.7k
      const int nPoints = json_object_array_length(coords);
289
69.8k
      for (int j = 0; j < nPoints; ++j)
290
2.14k
      {
291
2.14k
        json_object *coord = json_object_array_get_idx(coords, j);
292
2.14k
        if (!parse_geojson_coord(coord, hasz, pa))
293
22
        {
294
22
          lwmline_free(geom);
295
22
          ptarray_free(pa);
296
22
          return NULL;
297
22
        }
298
2.14k
      }
299
67.7k
      geom = lwmline_add_lwline(geom, lwline_construct(0, NULL, pa));
300
67.7k
    }
301
27
    else
302
27
    {
303
27
      lwmline_free(geom);
304
27
      ptarray_free(pa);
305
27
      return NULL;
306
27
    }
307
67.7k
  }
308
949
  return (LWGEOM *)geom;
309
998
}
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
8
    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
135k
  for (int i = 0; i < nPolys; ++i)
321
134k
  {
322
134k
    json_object *rings = json_object_array_get_idx(polys, i);
323
134k
    LWPOLY *poly = parse_geojson_poly_rings(rings, hasz);
324
134k
    if (poly)
325
100k
      geom = (LWGEOM *)lwmpoly_add_lwpoly((LWMPOLY *)geom, poly);
326
134k
  }
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.36k
{
334
2.36k
  json_object *poObjGeoms = findMemberByName(geojson, "geometries");
335
2.36k
  if (!poObjGeoms)
336
104
  {
337
104
    lwerror("Unable to find 'geometries' in GeoJSON string");
338
104
    return NULL;
339
104
  }
340
2.25k
  LWGEOM *geom = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, 0, 1, 0);
341
342
2.25k
  if (json_type_array == json_object_get_type(poObjGeoms))
343
1.36k
  {
344
1.36k
    const int nGeoms = json_object_array_length(poObjGeoms);
345
13.9k
    for (int i = 0; i < nGeoms; ++i)
346
12.7k
    {
347
12.7k
      json_object *poObjGeom = json_object_array_get_idx(poObjGeoms, i);
348
12.7k
      LWGEOM *t = parse_geojson(poObjGeom, hasz);
349
12.7k
      if (t)
350
12.5k
        geom = (LWGEOM *)lwcollection_add_lwgeom((LWCOLLECTION *)geom, t);
351
114
      else
352
114
      {
353
114
        lwgeom_free(geom);
354
114
        return NULL;
355
114
      }
356
12.7k
    }
357
1.36k
  }
358
359
2.14k
  return geom;
360
2.25k
}
361
362
static inline LWGEOM *
363
parse_geojson(json_object *geojson, int *hasz)
364
15.0k
{
365
15.0k
  json_object *type = NULL;
366
15.0k
  const char *name;
367
368
15.0k
  if (!geojson)
369
1
  {
370
1
    lwerror("invalid GeoJSON representation");
371
1
    return NULL;
372
1
  }
373
374
15.0k
  type = findMemberByName(geojson, "type");
375
15.0k
  if (!type)
376
301
  {
377
301
    lwerror("unknown GeoJSON type");
378
301
    return NULL;
379
301
  }
380
381
14.7k
  name = json_object_get_string(type);
382
383
14.7k
  if (strcasecmp(name, "Point") == 0)
384
5.36k
    return parse_geojson_point(geojson, hasz);
385
386
9.37k
  if (strcasecmp(name, "LineString") == 0)
387
536
    return parse_geojson_linestring(geojson, hasz);
388
389
8.83k
  if (strcasecmp(name, "Polygon") == 0)
390
662
    return parse_geojson_polygon(geojson, hasz);
391
392
8.17k
  if (strcasecmp(name, "MultiPoint") == 0)
393
3.01k
    return parse_geojson_multipoint(geojson, hasz);
394
395
5.15k
  if (strcasecmp(name, "MultiLineString") == 0)
396
999
    return parse_geojson_multilinestring(geojson, hasz);
397
398
4.15k
  if (strcasecmp(name, "MultiPolygon") == 0)
399
1.24k
    return parse_geojson_multipolygon(geojson, hasz);
400
401
2.91k
  if (strcasecmp(name, "GeometryCollection") == 0)
402
2.36k
    return parse_geojson_geometrycollection(geojson, hasz);
403
404
553
  lwerror("invalid GeoJson representation");
405
553
  return NULL; /* Never reach */
406
2.91k
}
407
408
#endif /* HAVE_LIBJSON */
409
410
LWGEOM *
411
lwgeom_from_geojson(const char *geojson, char **srs)
412
2.54k
{
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.54k
  json_tokener *jstok = json_tokener_new();
421
2.54k
  json_object *poObj = json_tokener_parse_ex(jstok, geojson, -1);
422
2.54k
  if (jstok->err != json_tokener_success)
423
204
  {
424
204
    char err[256];
425
204
    snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset);
426
204
    json_tokener_free(jstok);
427
204
    json_object_put(poObj);
428
204
    lwerror("%s", err);
429
204
    return NULL;
430
204
  }
431
2.33k
  json_tokener_free(jstok);
432
433
2.33k
  *srs = NULL;
434
2.33k
  json_object *poObjSrs = findMemberByName(poObj, "crs");
435
2.33k
  if (poObjSrs != NULL)
436
195
  {
437
195
    json_object *poObjSrsType = findMemberByName(poObjSrs, "type");
438
195
    if (poObjSrsType != NULL)
439
144
    {
440
144
      json_object *poObjSrsProps = findMemberByName(poObjSrs, "properties");
441
144
      if (poObjSrsProps)
442
48
      {
443
48
        json_object *poNameURL = findMemberByName(poObjSrsProps, "name");
444
48
        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
48
      }
454
144
    }
455
195
  }
456
457
2.33k
  int hasz = LW_FALSE;
458
2.33k
  LWGEOM *lwgeom = parse_geojson(poObj, &hasz);
459
2.33k
  json_object_put(poObj);
460
2.33k
  if (!lwgeom)
461
1.25k
    return NULL;
462
463
1.07k
  if (!hasz)
464
914
  {
465
914
    LWGEOM *tmp = lwgeom_force_2d(lwgeom);
466
914
    lwgeom_free(lwgeom);
467
914
    lwgeom = tmp;
468
914
  }
469
1.07k
  lwgeom_add_bbox(lwgeom);
470
1.07k
  return lwgeom;
471
2.33k
#endif /* HAVE_LIBJSON */
472
2.33k
}