PostGIS Tutorial for Developers: Store and Query Location Data in PostgreSQL

Suman SharmaSuman Sharma

If your app touches maps, farms, deliveries, or anything with a location, PostGIS turns PostgreSQL into a serious spatial database. I use it for plot data in compliance work. Here's the practical subset you need.

Setup

CREATE EXTENSION IF NOT EXISTS postgis; CREATE TABLE plots ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), producer_id uuid NOT NULL, geom geometry(Geometry, 4326) NOT NULL, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX plots_geom_idx ON plots USING GIST (geom);

geometry(Geometry, 4326) accepts points and polygons in WGS 84, the same system as GeoJSON.

Insert GeoJSON

INSERT INTO plots (producer_id, geom) VALUES ( $1, ST_SetSRID(ST_GeomFromGeoJSON($2), 4326) );

Export GeoJSON

SELECT json_build_object( 'type', 'Feature', 'properties', json_build_object('id', id), 'geometry', ST_AsGeoJSON(geom)::json ) FROM plots WHERE producer_id = $1;

Geometry vs geography

  • geometry: planar maths, fast, most functions. In 4326, units are degrees.
  • geography: calculations on the spheroid, results in metres.

Store as geometry, cast for measurements:

SELECT id, ST_Area(geom::geography) / 10000 AS area_ha FROM plots;

Common queries

Validity:

SELECT id, ST_IsValidReason(geom) FROM plots WHERE NOT ST_IsValid(geom);

Plots within the map viewport:

SELECT id, ST_AsGeoJSON(geom) FROM plots WHERE geom && ST_MakeEnvelope($1, $2, $3, $4, 4326);

Plots intersecting a forest-loss layer:

SELECT p.id FROM plots p JOIN forest_loss f ON ST_Intersects(p.geom, f.geom);

Nearest collection point:

SELECT c.id, ST_Distance(p.geom::geography, c.geom::geography) AS metres FROM plots p, collection_points c WHERE p.id = $1 ORDER BY p.geom <-> c.geom LIMIT 1;

Performance tips

  • GIST index on every spatial column you query.
  • Use && (bounding-box overlap) before expensive functions where possible.
  • Simplify geometries for display (ST_SimplifyPreserveTopology).
  • Subdivide huge polygons (ST_Subdivide) for faster intersections.

With an ORM

Most TypeScript ORMs don't model PostGIS types natively. I keep spatial queries in raw SQL (with parameters) and keep the rest in the ORM.

Frequently asked questions

Should I use geometry or geography in PostGIS?

Geography calculates distances and areas on the earth's surface in metres, which is convenient for global data. Geometry is faster and supports more functions. A common pattern is to store geometry in SRID 4326 and cast to geography for measurements.

What is SRID 4326?

The identifier for WGS 84 longitude/latitude, the coordinate system used by GPS and GeoJSON.

Do I need a spatial index?

Yes, for any table you query spatially. A GIST index on the geometry column makes intersection and bounding-box queries fast.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.