2026-11-03 –, Gardenia
Coordinates arrive in whatever projection the source used, occasionally several SRIDs in one file. We implemented a reusable Django GeneratedField that normalizes each row to one self-maintaining PostGIS point, now running in PoSSMM, West Virginia DNR's data platform.
Most of the coordinate data from West Virginia's Division of Natural Resources (DNR), Wildlife Resources Section doesn't show up in a clean, consistent format. It comes from the field, and it arrives in whatever projection the person or instrument was working in. For PoSSMM, the data platform we built for the DNR, that meant tables holding points in UTM across two zones, WGS84 decimal degrees, and a few others, all mixed together.
We needed those to end up as real PostGIS geometry in a single SRID so we could actually query and map them. We also didn't want to stand up a heavy GIS pipeline to get there as PoSSMM is a data platform that happens to have spatial data in it, not a GIS. The usual approaches all share the same weakness. You can transform the point in a Django model's save() method, or wire it up with a signal, but the moment you load data in bulk, which we do constantly, those get skipped and the geometry quietly drifts out of sync with the coordinates it's supposed to represent. We could've also simply rendered the X/Y points on a map in the front-end on the fly, but, with multiple SRIDs, all the coordinate transformation logic would live in JavaScript, which gets hard to maintain and feels clunky.
Instead, we handed the job to the database. Django 5's GeneratedField lets you define a column whose value Postgres computes from other columns. We used it to build a point with ST_MakePoint, tag it with the row's own SRID using ST_SetSRID, and reproject it to our storage SRID with ST_Transform. The result is an ordinary PostGIS point column that Postgres derives and maintains. If someone updates the X or Y, the point recomputes on its own. You can't write to it by hand, and it doesn't matter whether the row came in through the ORM, bulk_create, or raw SQL. The geometry is always right.
The part I like most is that we wrapped it into a small reusable field. Any model with an X, a Y, and an SRID column gets a maintained PostGIS point by adding one line. Adding spatial support to a new model is close to free now.
In five minutes I'll walk through the problem, why transform-on-save is fragile, the generated-column fix, and the reusable field we built on top of it. I'll also show the one edge that actually got us: ST_SetSRID wouldn't take a per-row SRID through the GeoDjango wrappers, so we dropped to a raw Func template to make it work. It's scoped to points and PostGIS, but the core idea, deriving and reprojecting geometry in the database instead of the application, carries to other stacks. You'll leave knowing how to do this in Django and why you'd want to.
Kirk Davis is a Senior Data Engineer at Timmons Group with over 13 years of GIS experience, specializing in leveraging large geospatial datasets through APIs, automation, and analytics. He focuses on solutions at the intersection of geospatial software, wildfire, and forestry.