Skip to main content
This page covers how geometry, geography, and raster columns behave in the Wherobots dbt adapter, how to load geometry from a seed file, and how to test spatial correctness.

Geometry

geometry columns behave like any other column in a model. Wherobots stores them in Havasu tables, and dbt docs generate reports them as geometry.
models/intermediate/int_trips_zoned.sql

Always set a spatial reference identifier

A spatial reference identifier (SRID) names the coordinate reference system a geometry is expressed in. Wherobots geometry constructors that take no SRID argument produce SRID 0, meaning “unknown”. A spatial join between geometries in different or unknown coordinate systems returns wrong results rather than an error. Normalize every geometry to a single SRID in your staging layer, before any cross-source operation. Two functions do different things:
  • ST_SetSRID relabels the coordinate metadata and leaves the coordinates alone. Use it to attach the SRID the data is already in, typically because the source arrived as SRID 0.
  • ST_Transform reprojects the coordinates into another coordinate reference system. Use it when the source is in a different CRS from the one you are normalizing to. See CRS Transformation.
When the source coordinates are already in EPSG:4326 and only the label is missing, ST_SetSRID on its own is enough:
models/staging/stg_zipcode_zones.sql
When the source is in another CRS, ST_SetSRID alone is wrong. It labels the geometry EPSG:4326 while leaving the original coordinates in place, which is how you get the wrong joins described above. Label the real source SRID, then reproject. For a source column geometry stored in Web Mercator (EPSG:3857) without an SRID, the staging expression is:
Some Wherobots-hosted datasets store geometries with SRID 0 and others with 4326, so check a source’s documented CRS before you label it rather than assuming it is normalized. Then test the result: see Testing spatial invariants.

Declaring geometry column types

A model’s YAML can declare a column’s data_type, including geometry. The adapter does not enforce dbt model contracts yet: a model with contract: {enforced: true} still builds as a plain create table as select, and the stored type comes from the select expression. One declaration does change what the adapter builds. When an incremental model adds a geometry column through on_schema_change='append_new_columns', the adapter uses a data_type: geometry(<srid>) declared for that column as its coordinate reference system. Declare it for a new geometry column that has no non-null values yet; see Handling schema changes. For example, to add a dropoff_geom column to the fct_trip_events model after the table exists, set on_schema_change='append_new_columns' in the model’s config, then declare the column:
models/marts/_marts.yml

Seed geometry columns

dbt seed can load geometry directly from a comma-separated values (CSV) file. Two configurations control it.
1

Declare the column as geometry

dbt cannot infer a geometry column from CSV text, so you must declare it with +column_types.The adapter creates the column as geometry(<srid>): an Apache Iceberg V3 geometry column, with one coordinate reference system (CRS) for every value in the column. To set the SRID in the type itself, write it there, for example geom: geometry(3857).
dbt_project.yml
2

Set the SRID for the column

+column_srids maps a column name to an integer SRID. The adapter uses that SRID as the column’s CRS and applies it to every value that does not state its own SRID.
dbt_project.yml
+column_srids is optional. When it is unset, the adapter uses 4326 (WGS 84). Set the SRID in one place. The adapter fails a seed that declares geometry(3857) in +column_types and 4326 in +column_srids for the same column.
3

Write the CSV

Each cell may use any of the four supported formats, and a single column may mix them.
seeds/landmark_points.csv
4

Load the seed

Supported geometry formats

How the SRID is resolved

The column gets one SRID, and the adapter never emits SRID 0:
  1. The SRID in the type, as in +column_types: {geom: geometry(3857)}.
  2. Otherwise the +column_srids value, if you set one for that column.
  3. Otherwise 4326.
WKT, GeoJSON, and WKB values get the column’s SRID. An EWKT value is written with its own inline SRID, so that SRID must equal the column’s. Wherobots rejects a value in any other SRID, and the seed fails with Cannot write geometry with SRID 3857 into a column with SRID 4326. To load data from another coordinate system, reproject it to the column’s SRID before you seed it, or put it in a separate seed with its own SRID. Two cases fail the seed instead of loading a geometry you probably do not want:
  • SRID=0;... in an EWKT value. Passing it through would store an unknown coordinate system, and substituting another SRID would overwrite one the file states outright. The seed fails instead.
  • A zero or negative +column_srids value. Configure a real SRID for the column, or use EWKT values that state their own.
An empty CSV cell loads as NULL, not as an empty geometry.
An extended WKB (EWKB) value can carry its own SRID, but the adapter does not read it. Every WKB value gets the column’s SRID, with no reprojection and no error. For example, an EWKB point in EPSG:3857 seeded into a 4326 column loads with its Web Mercator coordinates labeled 4326. Reproject WKB data to the column’s SRID before you seed it, or set the column’s SRID to the data’s SRID when every value in the column uses it.

Geography

geography columns can be stored. A table, view, or incremental model that selects a geography expression, such as ST_GeogFromWKT(wkt, 4326), materializes a geography column (an Iceberg V3 type), and dbt docs generate reports it as geography. Later incremental runs keep writing to an existing geography column. Adding a new geography column to an existing incremental table through on_schema_change='append_new_columns' is not supported: the adapter would add a bare geography column, which the engine rejects even when the YAML declares data_type: geography(4326). Rebuild the model with --full-refresh to add one. A bare geography column definition is still rejected by the engine with UNSUPPORTED_DATATYPE because it declares no coordinate reference system, so a seed column cannot be declared geography. Most geodesic work does not need the type at all. Spheroidal and great-circle functions such as ST_DistanceSphere and ST_DistanceSpheroid take geometry arguments in EPSG:4326 and return a plain number, so the result stores as an ordinary double:
models/intermediate/int_zone_landmarks.sql
The same applies to area. ST_Area on an EPSG:4326 geometry returns square degrees, which is not a usable unit. Use ST_AreaSpheroid, which returns square meters. The next example counts Overture places per ZIP code, so it first stages the places inside the study area:
models/staging/stg_overture_places.sql
models/marts/fct_place_density_by_zone.sql
To store a geography value as text instead, for consumers that cannot read the Iceberg V3 geography type, serialize it:
ST_AsEWKT(), ST_AsText(), ST_AsBinary(), and ST_AsEWKB() serialize geography values, and ST_SRID() reads their SRID. ST_AsGeoJSON(), ST_AsGML(), ST_AsKML(), and ST_GeoHash() accept geometry only and reject a geography value, so convert it with ST_GeogToGeometry() first.
Declaring geography in a seed +column_types returns the engine’s raw UNSUPPORTED_DATATYPE error rather than a dbt-level message: the seed’s create table spells out the column type, and a bare geography declares no coordinate reference system. A model contract’s data_type: geography does not cause this error, because contracts do not reach the table DDL.

Raster

raster columns pass through untouched. They build in models, survive create table as select, appear as raster in dbt docs generate, and come back from dbt show. You cannot seed a raster. A raster value has no compact text encoding that fits a CSV cell, so +column_types: {col: raster} is not supported. Load rasters through RasterFlow or a Wherobots notebook, then reference the resulting table as a dbt source.

Testing spatial invariants

The adapter does not ship spatial generic tests. Write spatial checks as singular tests: a SQL file under tests/ that returns the rows breaking the rule. The test passes when it returns nothing.

Assert a consistent SRID

tests/assert_geometries_srid_4326.sql

Assert geometries are topologically valid

Self-intersecting polygons and other malformed shapes corrupt spatial joins without raising an error, so check for them explicitly.
tests/assert_geometries_valid.sql

Assert geometries fall inside an expected area

tests/assert_pickups_in_nyc.sql
Run them with the rest of your tests:
Generic tests such as unique, not_null, relationships, and accepted_values work on the non-spatial columns of the same models, so a single dbt test run covers both.

Next steps

Materializations

Views, tables, ephemeral models, incremental strategies, snapshots, and seeds.

Geometry functions

The full Spatial SQL function reference available to your models.

Adapter overview

Install, configure a profile, and verify the connection.