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 GEOMETRY in 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 widest ST_* function set. This is the default for most analytics work; enforce the projection through spatial reference system management.
  • Choose GEOGRAPHY when 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, and GEOGRAPHY computes 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.

Planar chord versus spheroidal great-circle distance between two points A cross-section of the curved Earth surface with two points A and B. The GEOGRAPHY type measures the true great-circle arc that follows the surface between the points, drawn as a thick teal curve. The GEOMETRY type on unprojected latitude and longitude measures a straight planar chord between the points, drawn as an orange dashed line that cuts beneath the surface and is shorter than the true distance, so it underestimates and distorts real-world distance. A short summary strip notes that GEOMETRY in a projected SRID is fast with broad function coverage while GEOGRAPHY is spheroidally correct at higher compute cost. Earth surface (spheroid cross-section) A B GEOGRAPHY · great-circle arc (correct surface distance) GEOMETRY on lat/long · straight chord (shorter, distorted) GEOMETRY (projected) fast · broad ST_* · local accuracy GEOGRAPHY (spheroid) correct global distance · more CPU wrong choice silent distance errors

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-postgres against PostGIS 3.x (supports both types), dbt-bigquery (GEOGRAPHY only), or dbt-snowflake (both). Compare them in choosing the right spatial adapter.
  • A declared canonical SRID for GEOMETRY storage — 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_type is 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 GEOMETRY in a projected SRID for speed and cast to ::geography only where a specific query needs spheroidal distance, or store GEOGRAPHY outright 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 that GEOGRAPHY columns are indexed with GiST just like GEOMETRY, 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 planar GEOMETRY type and no projected-SRID storage; all distance is great-circle in metres. This removes the decision but also removes planar ST_Buffer semantics and any projected-metric workflow.
  • Snowflake offers both GEOMETRY (planar, SRID-aware) and GEOGRAPHY (spheroidal, WGS 84). The gotcha is casting: wrapping a projected GEOMETRY in TO_GEOGRAPHY re-interprets its coordinates as lon/lat and silently corrupts the result. Standardize the storage type per model and cast deliberately.
  • DuckDB spatial is planar GEOMETRY only. It is ideal for fast local and CI validation of the planar subset, but any GEOGRAPHY-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:

sql
-- 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:

yaml
# 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_Distance on unprojected EPSG:4326 GEOMETRY returns degrees, not metres — a 500 threshold then means 500 degrees and matches everything. Either store projected GEOMETRY or cast to GEOGRAPHY.
  • Silent TO_GEOGRAPHY corruption. On Snowflake and PostGIS, casting a projected GEOMETRY to geography re-reads its coordinates as lon/lat. Only cast columns that are actually in a lon/lat frame.
  • GEOGRAPHY buffers surprise people. A buffer computed on GEOGRAPHY is not the same shape as a planar ST_Buffer in a projected SRID. For metric buffering, project to GEOMETRY, buffer, then transform back if needed.
  • Mixed types in one calculation. Combining a GEOMETRY and a GEOGRAPHY argument 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. Choosing GEOGRAPHY does not remove the need for a spatial index. On PostGIS a GEOGRAPHY column still needs a GiST index built in a post_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.

Up: Part of Choosing the Right Spatial Adapter.