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_SetSRIDrelabels 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_Transformreprojects 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.
ST_SetSRID on its own is enough:
models/staging/stg_zipcode_zones.sql
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:
Declaring geometry column types
A model’s YAML can declare a column’sdata_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:- The SRID in the type, as in
+column_types: {geom: geometry(3857)}. - Otherwise the
+column_sridsvalue, if you set one for that column. - Otherwise 4326.
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_sridsvalue. Configure a real SRID for the column, or use EWKT values that state their own.
NULL, not as an empty geometry.
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
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
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.
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 undertests/ 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
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.

