Geometry vs Geography
PostGIS geometry uses planar math in a projected coordinate system. geography uses a spheroid model on WGS84 for geodesic distances in meters. Pick the type that matches your SRID and distance accuracy needs.
Recipe
-- Same lon/lat, different types
SELECT
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326) AS geom_4326,
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326)::extensions.geography AS geog;
-- Distance NYC to LA
SELECT extensions.ST_Distance(
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326)::extensions.geography,
extensions.ST_SetSRID(extensions.ST_MakePoint(-118.2437, 34.0522), 4326)::extensions.geography
) AS meters_geog;
SELECT extensions.ST_Distance(
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326),
extensions.ST_SetSRID(extensions.ST_MakePoint(-118.2437, 34.0522), 4326)
) AS degrees_geom_wrong_for_meters;When to reach for this: Choosing column types for new tables, debugging distance bugs, or optimizing indexes for ST_DWithin.
Working Example
CREATE TABLE delivery_zones (
id serial PRIMARY KEY,
name text,
-- City-scale polygon: local UTM for accurate geometry math
zone_geom extensions.geometry(Polygon, 32618), -- UTM 18N for NYC area
-- Driver location queries: geography for meter radius from GPS
service_geog extensions.geography(Polygon, 4326)
);
-- Store both when needed: geometry for editing in local projection,
-- geography for "within 2 km of driver" queries
CREATE INDEX delivery_zones_geog_gist
ON delivery_zones USING gist (service_geog);
SELECT name
FROM delivery_zones
WHERE extensions.ST_DWithin(
service_geog,
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.99, 40.75), 4326)::extensions.geography,
2000
);What this demonstrates:
- Local projected
geometryfor city planning polygons geographyfor GPS radius filters in meters- GiST on geography supports
ST_DWithin - Mixed models are valid when transforms are documented
Deep Dive
Comparison Table
| Aspect | geometry | geography |
|---|---|---|
| Coordinate system | Projected (meters in plane) | WGS84 spheroid |
| Distance units | Map units (meters if projected) | Meters on spheroid |
| Performance | Faster for local areas | Slower, more accurate globally |
| Functions | Full planar suite | Subset; casts often needed |
| SRID | Must be projected for meter math | Typically 4326 |
SRID Quick Reference
| SRID | Name | Use |
|---|---|---|
| 4326 | WGS84 lat/lon | GPS, web maps, geography |
| 3857 | Web Mercator | Display only; poor for distance |
| 32618 | UTM 18N | NYC region planar work |
| 0 | Unknown | Avoid in production |
SELECT srid, auth_name, auth_srid, srtext
FROM spatial_ref_sys
WHERE srid IN (4326, 3857, 32618);Transforms
-- Reproject geometry between SRIDs
SELECT extensions.ST_Transform(
extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326),
32618
);
-- Geography from geometry (4326 only for geog cast)
SELECT extensions.ST_SetSRID(extensions.ST_MakePoint(-73.9857, 40.7484), 4326)::extensions.geography;Index Considerations
- GiST works on both geometry and geography.
- Use geometry with correct local projection for large polygon overlays (faster).
- Use geography for consistent meter radii on lat/lon inputs without projection math in app.
Gotchas
- 4326 geometry distance in meters -
ST_Distanceon 4326 geometry returns degrees. Fix: Cast to geography or transform to UTM. - 3857 for analytics - Web Mercator distorts area and distance. Fix: 3857 for tiles only; store 4326 or local UTM.
- SRID 0 insert -
ST_GeomFromText('POINT(0 0)')without SRID. Fix: Typmodgeometry(Point,4326)rejects wrong SRID when enforced. - geography on non-4326 - Limited support; wrong results. Fix: Transform to 4326 before geography cast.
- Mixing types in ST_Intersects - Implicit cast surprises. Fix: Explicit cast and document pipeline.
- Poles and dateline - geography handles; bad polygons in naive geometry. Fix: Validate with
ST_IsValid.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| geometry + UTM | City/county fixed region | Global roaming queries |
| geography only | Simple GPS radius apps | Heavy polygon overlay performance pain |
| Haversine in app | One-off distance | Spatial joins and indexes needed |
| Raster tiles | Display-only maps | Vector feature storage |
FAQs
Which type for delivery radius?
geography on 4326 for meter ST_DWithin from GPS points.Polygon storage?
Local city: projected geometry. Global service areas: geography or multi-zone UTM splits.ST_Length units?
geometry: map units; geography: meters on spheroid.Area calculations?
Use appropriate projection; geography area is spheroid-based.Typmod enforcement?
geometry(Point,4326) rejects wrong SRID inserts in recent PostGIS.Performance tip?
Bounding box && operator on geometry geography casts uses index prefilter.EWKT?
SRID=4326;POINT(...) in COPY for explicit SRID ingest.3D geography?
Rare; usually drop Z for web apps.QGIS export?
Match SRID in export settings to column typmod.Testing?
Known distance pairs (NYC-LA ~3935 km) sanity check queries.Related
- PostGIS Basics - when PostGIS vs tiles
- Spatial Indexes - GiST usage
- PostGIS Performance - bounds prefilter
- Data Types JSONB - composite patterns
- PostGIS Best Practices - SRID on every geometry
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, and Patroni 3.x.