Geometry vs Geography Type Trade-offs
This page helps you decide whether a spatial column in your dbt project should be typed GEOMETRY (planar, in a projected SRID) or GEOGRAPHY (spheroidal, measuring true great-circle distance) — and what that choice costs in accuracy, speed, and function coverage.
When to use this approach
The distinction is not cosmetic. GEOMETRY treats coordinates as points on a flat Cartesian plane; GEOGRAPHY treats them as positions on a spheroid. Getting it wrong produces distances that are silently, sometimes catastrophically, incorrect. Decide by the question your models actually answer:
- Choose
GEOMETRYin a projected SRID when your data fits inside one local zone (a UTM zone, a state plane) and you need fast, exact metric operations — area, buffer, overlay, and distance — plus the widestST_*function set. This is the default for most analytics work; enforce the projection through spatial reference system management. - Choose
GEOGRAPHYwhen distances or areas span continents, cross projection zones, or must be correct on raw latitude/longitude without you managing a projection. Great-circle distance on a spheroid is the only correct answer for globe-spanning proximity, andGEOGRAPHYcomputes it directly. - Let the engine decide for you when the warehouse only offers one type. BigQuery GIS is
GEOGRAPHY-only; the trade-off is made at adapter-selection time, which is why this decision belongs alongside choosing the right spatial adapter.
A useful heuristic: if you can name a single projected CRS whose units are metres and whose distortion is acceptable across your whole dataset, use GEOMETRY in that SRID. If you cannot, use GEOGRAPHY.
Why the type changes the answer
The clearest way to see the difference is a cross-section of the Earth. Two points that are far apart have a chord — the straight line through the planar interpretation — and an arc along the surface, which is the true great-circle distance. GEOMETRY on unprojected lat/long measures neither correctly; GEOGRAPHY measures the arc.
The chord is shorter than the arc, so a planar distance on lat/long underestimates the real-world separation — and the error grows with distance and latitude. Inside a single projected zone the distortion is negligible and GEOMETRY is both faster and more capable. Across a continent it is meaningless, and GEOGRAPHY is the correct type.
Prerequisites
- A spatial-capable adapter:
dbt-postgresagainst PostGIS 3.x (supports both types),dbt-bigquery(GEOGRAPHYonly), ordbt-snowflake(both). Compare them in choosing the right spatial adapter. - A declared canonical SRID for
GEOMETRYstorage — a metric projected system if you need accurate distance/area, governed through spatial reference system management. - A dbt schema contract so the column’s declared
data_typeis enforced and an incremental run cannot silently coerce one type into the other.
The decision, axis by axis
Accuracy
GEOMETRY is exact for the plane it is projected onto and wrong everywhere else. On unprojected EPSG:4326 it measures degrees, which is not a physical distance at all. In a well-chosen projected SRID its distances and areas are accurate to within the projection’s distortion — typically sub-metre inside a UTM zone. GEOGRAPHY computes true spheroidal distance on raw lon/lat everywhere on Earth, with no projection to choose and no zone boundary to respect. For a global feed, GEOGRAPHY is correct by construction; for a city-scale feed, projected GEOMETRY is correct and cheaper.
Performance
GEOMETRY operations are planar arithmetic and run fastest, with full planar index support. GEOGRAPHY operations solve spheroidal geometry — more CPU per predicate, and some planar index optimizations do not apply. On PostGIS the difference is real enough that casting a large GEOMETRY column to ::geography inside a hot join can dominate query time. Where both are viable, GEOMETRY in a projected SRID is the performance choice.
Function coverage
GEOMETRY has the broadest ST_* surface: overlay, topology, raster interplay, and constructors that have no spheroidal analogue. GEOGRAPHY implements a curated subset — enough for distance, containment, and intersection, but not the full planar toolkit. If a model needs ST_Buffer with planar semantics, ST_Simplify at a metric tolerance, or topology operations, GEOMETRY is often the only practical type; a GEOGRAPHY buffer is computed differently and may not mean what you expect.
Decision table
| Consideration | GEOMETRY (projected SRID) |
GEOGRAPHY (spheroid) |
|---|---|---|
| Distance accuracy | Exact within the projection; wrong outside it | Correct great-circle distance everywhere |
| Best data extent | Fits one zone (UTM, state plane) | Continental / global, crosses zones |
| Units | CRS units (metres in a metric SRID) | Always metres |
| Performance | Fastest; full planar index use | More CPU; some planar optimizations lost |
| Function coverage | Broadest ST_*, topology, raster |
Curated subset (distance, contains, intersects) |
| Projection management | You pick and enforce an SRID | None to manage — raw lon/lat |
| Typical role | City/region analytics, overlays, buffers | Global proximity, routing distance, cross-zone |
Per-engine notes
Engine choice constrains — and sometimes makes — this decision.
- PostGIS supports both types natively. You can store
GEOMETRYin a projected SRID for speed and cast to::geographyonly where a specific query needs spheroidal distance, or storeGEOGRAPHYoutright for globe-spanning data. This flexibility is why PostGIS is the reference engine for mixed workloads; set it up via setting up PostGIS with dbt. Note thatGEOGRAPHYcolumns are indexed with GiST just likeGEOMETRY, so index selection still matters — see GiST vs SP-GiST index selection. - BigQuery GIS is
GEOGRAPHY-only, fixed to EPSG:4326, with spheroidal semantics and transparent indexing. There is no planarGEOMETRYtype and no projected-SRID storage; all distance is great-circle in metres. This removes the decision but also removes planarST_Buffersemantics and any projected-metric workflow. - Snowflake offers both
GEOMETRY(planar, SRID-aware) andGEOGRAPHY(spheroidal, WGS 84). The gotcha is casting: wrapping a projectedGEOMETRYinTO_GEOGRAPHYre-interprets its coordinates as lon/lat and silently corrupts the result. Standardize the storage type per model and cast deliberately. - DuckDB spatial is planar
GEOMETRYonly. It is ideal for fast local and CI validation of the planar subset, but anyGEOGRAPHY-dependent numeric assertion must be checked on the production engine — the CI trade-off is detailed in PostGIS vs DuckDB spatial for CI pipelines.
Applying it in a dbt model
Store GEOMETRY in a projected SRID and cast to geography only at the point a query needs spheroidal distance, keeping the fast planar type as the storage default:
-- models/marts/fct_store_reach.sql
{{ config(materialized='table') }}
with stores as (
select
store_id,
ST_Transform(geom, {{ var('canonical_srid', 26910) }}) as geom -- projected GEOMETRY
from {{ ref('stg_stores') }}
)
select
a.store_id,
b.store_id as neighbor_id,
-- cast to geography only where true metres over long distance matter
ST_Distance(a.geom::geography, b.geom::geography) as great_circle_m
from stores a
join stores b
on a.store_id <> b.store_id
Pin the storage type with a schema contract so an incremental run cannot coerce it:
# models/marts/_marts.yml
version: 2
models:
- name: fct_store_reach
config:
contract:
enforced: true
columns:
- name: geom
data_type: geometry
Gotchas & edge cases
- Distance in degrees.
ST_Distanceon unprojected EPSG:4326GEOMETRYreturns degrees, not metres — a500threshold then means 500 degrees and matches everything. Either store projectedGEOMETRYor cast toGEOGRAPHY. - Silent
TO_GEOGRAPHYcorruption. On Snowflake and PostGIS, casting a projectedGEOMETRYto geography re-reads its coordinates as lon/lat. Only cast columns that are actually in a lon/lat frame. GEOGRAPHYbuffers surprise people. A buffer computed onGEOGRAPHYis not the same shape as a planarST_Bufferin a projected SRID. For metric buffering, project toGEOMETRY, buffer, then transform back if needed.- Mixed types in one calculation. Combining a
GEOMETRYand aGEOGRAPHYargument forces an implicit cast that can be wrong or slow. Keep both sides of a predicate the same type; enforce it with a schema contract. - Index still matters for
GEOGRAPHY. ChoosingGEOGRAPHYdoes not remove the need for a spatial index. On PostGIS aGEOGRAPHYcolumn still needs a GiST index built in apost_hook, or predicates degrade to sequential scans.
FAQ
Which type should be my default in a dbt project?
Default to GEOMETRY in a projected metric SRID when your data fits inside one zone, because it is faster, has the broadest ST_* coverage, and gives exact local distance and area. Switch to GEOGRAPHY only when the data spans zones or the globe and you need correct great-circle distance without managing a projection. Declare whichever you pick as an enforced data_type in a schema contract.
Is GEOGRAPHY always more accurate than GEOMETRY?
No. GEOGRAPHY is more accurate for long distances and cross-zone data because it measures true great-circle distance on a spheroid. But inside a well-chosen projected SRID, GEOMETRY is accurate to within the projection’s distortion — often sub-metre — while being faster and more capable. Accuracy depends on matching the type to the extent of your data, not on the type alone.
Can I store GEOMETRY and compute distance as GEOGRAPHY?
Yes, and on PostGIS this is a common pattern: store projected GEOMETRY for fast planar operations and cast to ::geography only in the specific predicate that needs spheroidal distance. Keep the cast out of hot join keys where possible, since casting a large column inline can dominate query time. Only cast columns that are genuinely in a lon/lat frame.
My warehouse only has GEOGRAPHY — what do I lose?
On a GEOGRAPHY-only engine such as BigQuery GIS you lose projected-metric workflows and planar ST_Buffer/ST_Simplify semantics, and everything is great-circle in metres on EPSG:4326. For global distance and containment that is fine; for city-scale overlays or metric buffering you would otherwise reach for planar GEOMETRY, so plan those transformations around the spheroidal functions the engine does provide.
How do I stop dbt from silently changing the type on incremental runs?
Declare the column’s data_type under an enforced schema contract in the model’s YAML. With contract: enforced: true, dbt checks the materialized column type against the declared one and fails the run if a transformation would coerce GEOMETRY into GEOGRAPHY or vice versa, so the change is caught in CI rather than in production distances.
Related
- Choosing the Right Spatial Adapter — the engine’s type system is part of this decision.
- PostGIS vs DuckDB Spatial for CI Pipelines — why
GEOGRAPHYassertions belong on the production engine, not the CI gate. - Spatial Reference System Management — pick and enforce the projected SRID that makes
GEOMETRYaccurate.
Up: Part of Choosing the Right Spatial Adapter.