-
Notifications
You must be signed in to change notification settings - Fork 432
Description
I imagine there are at least a few people (like me, lol) using PostgreSQL with PostGIS wondering if there's an elegant, precise way to encode geometry/geography types for queries.
A naive approach might be to use the string-based functions:
await conn.fetchrow(
'INSERT INTO locations (name, coords) VALUES ($1, ST_GeographyFromText($2))',
'Empire State Building',
'Point(-73.985661 40.748447)',
)
These string-based functions have the potential for precision loss (due to string conversions), and there are faster alternatives available, like ST_MakePoint
:
ST_MakePoint while not being OGC compliant is generally faster and more precise than ST_GeomFromText and ST_PointFromText. It is also easier to use if you have raw coordinates rather than WKT.
You could do something like this:
await conn.fetchrow(
'INSERT INTO locations (name, coords) VALUES ($1, ST_MakePoint($2, $3))',
'Empire State Building',
-73.985661,
40.748447,
)
Better, but specific to the point type.
A fully general solution that suffers no loss of precision can be implemented using Connection.set_type_codec
. Geometry/geography objects are encoded in the well-known binary format. It works for any Python object that conforms to the geo interface specification, and relies on the excellent Shapely library (but anything that speaks WKB will do).
from typing import Any
import shapely.geometry
import shapely.wkb
from shapely.geometry.base import BaseGeometry
def encode_geometry(geometry: Any) -> bytes:
if not hasattr(geometry, '__geo_interface__'):
raise TypeError(f'{geometry} does not conform to geo interface')
shape = shapely.geometry.asShape(geometry)
return shapely.wkb.dumps(shape)
def decode_geometry(wkb: bytes) -> BaseGeometry:
return shapely.wkb.loads(wkb)
await conn.set_type_codec(
'geometry', # also works for 'geography'
encoder=encode_geometry,
decoder=decode_geometry,
format='binary',
)
With a connection configured as above, the query is as simple as:
await conn.fetchrow(
'INSERT INTO locations (name, coords) VALUES ($1, $2)',
'Empire State Building',
shapely.geometry.Point(-73.985661, 40.748447), # or any other geo-interface compliant type
)
And it works for polygons and line strings too!
This may be rather niche but it took me a good deal of trial-and-error to figure out. It would be really cool to see an example like the above in the documentation. Maybe alongside the JSON example under Custom Type Conversions?