One task I have come across repeatedly while building APIs or functions that accept GeoJSON is dealing with differing representations. The input data may be a geometry, a feature, or a feature collection. When utilizing PostGIS as a backend, I inevitably run into errors after naively feeding input data directly into the ST_GeomFromGeoJSON() function:
[XX000] ERROR: invalid GeoJson representation
Since the ST_GeomFromGeoJSON() function only operates on geometries, any input data represented as a feature or a feature collection will lead to this error.
To deal with this more rigorously, the JSON functions found in PostgreSQL can be utilized in a function that consistently provides data back as a table with PostGIS geometry objects.
There are three representations of GeoJSON data to account for.
- Feature collections can contain multiple features, each with a geometry and associated properties. This is the full example below.
- Features represent a single geometry but can preserve properties. A single feature would be a single object from the
featuresarray in the example below. - Geometries are only the geometry object itself. The geometry in the example below is the
geometryobject which defines coordinates and a type, but no additional properties.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[
-117.64357831688176,
46.70981365574963
],
[
-117.64357831688176,
45.01441557906168
],
[
-114.88052636647359,
45.01441557906168
],
[
-114.88052636647359,
46.70981365574963
],
[
-117.64357831688176,
46.70981365574963
]
]
],
"type": "Polygon"
}
}
]
}
Constructing a function that handles these three representations and provides back a standardized table of data complete with a PostGIS geometry is accomplished using the JSON functions within PostgreSQL.
drop function if exists public.geojsontogeom;
create function public.geojsontogeom(geojson jsonb)
returns table (geometrytype text, properties jsonb, geom geometry)
language plpgsql
as $function$
begin
return query
select f.features -> 'geometry' ->> 'type' geometrytype
,f.features -> 'properties' properties
,st_setsrid(st_geomfromgeojson(f.features ->> 'geometry'),4326) geometry
from (
select jsonb_array_elements(case
when lower(geojson ->> 'type')='featurecollection' then geojson -> 'features'
when lower(geojson ->> 'type')='feature' then jsonb_build_array(geojson)
else jsonb_build_array(jsonb_build_object('type','Feature','geometry',geojson)) end) features
) f;
end
$function$;
The function will accept all three formats and can be called like a table:
select * from public.geojsontogeom('<geojson>');
When a feature collection contains multiple features, they will be represented as rows in the results:
| geometrytype | properties | geometry |
|---|---|---|
| Polygon | {} | POLYGON((-117.643578… |
| Polygon | {} | POLYGON((-113.997757… |
| LineString | {} | LINESTRING(-112.2041… |
The Inverse
If you have a table of geometry data and want to represent it as a feature collection, you can construct the appropriate GeoJSON using the built in JSON functions of PostgreSQL.
select jsonb_build_object('type','FeatureCollection',
'features',jsonb_agg(st_asgeojson(features)::jsonb)) geojson
from public.twogeoms features;