Overview of GeoAlchemy 2
mainSQLAlchemy, extending its capabilities to support geographic and geometry data types and spatial functions within the SQLAlchemy ecosystem.repository·main·Indexed 20 days ago
https://github.com/geoalchemy/geoalchemy2A spatial extension for SQLAlchemy that enables the use of spatial databases. It provides support for Geometry, Geography, and Raster types, including integration with PostGIS and SpatiaLite. The library includes tools for defining spatial tables, performing spatial relationship queries using concise patterns, and configuring Alembic migrations via geoalchemy2.alembic_helpers to handle spatial operations and internal tables.
SQLAlchemy, extending its capabilities to support geographic and geometry data types and spatial functions within the SQLAlchemy ecosystem.GeoAlchemy 2 provides extensions for SQLAlchemy to work with various spatial databases. While it focuses on PostGIS (supporting PostGIS 2 and 3), it also supports the following dialects:
Note: Using non-PostGIS dialects may require specific application-side configuration and may not be optimal for performance.
GeoAlchemy2 allows you to perform spatial relationship queries (like ST_Contains, ST_Intersects, etc.) using two patterns:
func pattern: func.ST_Contains(column, geometry).column.ST_Contains(geometry). This pattern applies the function directly to the column object and is more readable.Note that GeoAlchemy2 operator functions (like .intersects()) do not include the ST_ prefix, whereas the standard PostGIS functions do.
from sqlalchemy import select, func
# Using func.ST_Contains
s = select(lake_table).where(
func.ST_Contains(lake_table.c.geom, 'POINT(4 1)')
)
# Using GeoAlchemy2 concise syntax
s = select(lake_table).where(
lake_table.c.geom.ST_Contains('POINT(4 1)')
)
# Using an operator function (no ST_ prefix)
s = select(lake_table).where(
lake_table.c.geom.intersects('LINESTRING(2 1,4 1)')
)GeoAlchemy 2 replaces several legacy spatial element classes with two primary classes: WKTElement and WKBElement.
Use geoalchemy2.elements.WKTElement in expressions when you need to specify a geometry using a Well-Known Text (WKT) string with a specific SRID. If no SRID is required, you can pass a WKT string directly to the spatial function.
Example with SRID:
from geoalchemy2.elements import WKTElement
Lake.geom.ST_Touches(WKTElement('POINT(1 1)', srid=4326))Example without SRID:
Lake.geom.ST_Touches('POINT(1 1)')geoalchemy2.elements.WKBElement is the type used for geometry values read from the database. When you load an object from the database, its geometry attributes will be instances of WKBElement. This replaces the legacy PersistentSpatialElement and PGPersistentSpatialElement classes.
from geoalchemy2.elements import WKTElement
# Using WKTElement with a specific SRID
Lake.geom.ST_Touches(WKTElement('POINT(1 1)', srid=4326))
# Using a raw WKT string when SRID is not needed
Lake.geom.ST_Touches('POINT(1 1)')GeoAlchemy 2 uses the standard spatial coordinate order of X Y (for geographic coordinates, this is longitude latitude) for most APIs.
However, SQL Server's native geography::Point constructor expects latitude before longitude (geography::Point(latitude, longitude, srid)).
GeoAlchemy handles this discrepancy automatically in specific scenarios (like computed columns), but when writing manual SQL or using certain functions, you must be aware of this difference.
GeoAlchemy 2 is designed to be a simpler, more maintainable extension for SQLAlchemy. Key features include:
geometry, geography, and raster types.func syntax (e.g., func.ST_GeomFromText(...)) rather than a separate namespace.to_shape and from_shape functions for improved integration with the Shapely library.You can define spatial relationships (e.g., finding all 'Treasures' contained within a 'Lake') using SQLAlchemy's relationship() function.
Because spatial relationships rely on SQL functions (like ST_Contains) rather than simple equality, you must use the .as_comparison(1, 2) method on the function in the primaryjoin argument. This is required for SQLAlchemy to correctly handle the function in a join condition.
Key parameters for spatial relationships:
primaryjoin: The SQL condition for the join, using func.ST_... and .as_comparison().viewonly=True: Recommended for spatial relationships to ensure they are used for loading data and not for attempting to persist changes via the relationship.backref: Provides the inverse relationship on the related model.from sqlalchemy.orm import relationship, backref
from sqlalchemy import func
class Lake(Base):
__tablename__ = 'lake'
id = Column(Integer, primary_key=True)
geom = Column(Geometry('POLYGON'))
treasures = relationship(
'Treasure',
primaryjoin='func.ST_Contains(foreign(Lake.geom), Treasure.geom).as_comparison(1, 2)',
backref=backref('lake', uselist=False),
viewonly=True,
uselist=True,
)To run tests directly on a Linux host, you must install the necessary system-level packages for PostgreSQL/PostGIS, Python development, MSSQL (via ODBC), SpatiaLite, and MySQL.
# PostgreSQL and PostGIS
$ sudo apt-get install postgresql postgresql-14-postgis-3 postgresql-14-postgis-3-scripts
# Python and PostgreSQL development
$ sudo apt-get install python3-dev libpq-dev libgeos-dev
# SpatiaLite
$ sudo apt-get install libsqlite3-mod-spatialite
# MySQL
$ sudo apt-get install mysql-client mysql-server default-libmysqlclient-dev
# Python dependencies
$ pip install -r requirements.txt -r requirements-mypy.txt
$ pip install psycopg2-binary pyodbc "Shapely>=1.3.0"To ensure Alembic's --autogenerate feature works correctly with GeoAlchemy 2 (handling imports, spatial operations, and ignoring internal spatial tables), you should configure the env.py file with geoalchemy2.alembic_helpers.
Pass the following three functions to context.configure() in both run_migrations_offline() and run_migrations_online():
include_object=alembic_helpers.include_object: Ignores internal tables managed by spatial extensions.process_revision_directives=alembic_helpers.writer: Adds specific spatial operations (like create_geospatial_table, add_geospatial_column, etc.) to the migration scripts.render_item=alembic_helpers.render_item: Automatically adds geoalchemy2 imports to the generated migration scripts.from geoalchemy2 import alembic_helpers
# In run_migrations_offline and run_migrations_online:
def run_migrations_online():
# ...
context.configure(
# ...
include_object=alembic_helpers.include_object,
process_revision_directives=alembic_helpers.writer,
render_item=alembic_helpers.render_item,
)When defining MSSQL computed columns for Geography types, you can use the standard ST_POINT(longitude, latitude) syntax. GeoAlchemy will automatically rewrite this to the correct SQL Server constructor syntax geography::Point(latitude, longitude, srid) during DDL generation.
from sqlalchemy import Column, Computed, Float, Integer, MetaData, Table
from geoalchemy2 import Geography
metadata = MetaData()
places = Table(
"places",
metadata,
Column("id", Integer, primary_key=True),
Column("longitude", Float, nullable=False),
Column("latitude", Float, nullable=False),
Column(
"geog",
Geography(geometry_type="POINT", srid=4326),
Computed("ST_POINT(longitude, latitude)", persisted=True),
),
)If you use custom SQLAlchemy types (e.g., TheCustomType defined in my_package.custom_types), Alembic's autogenerate won't automatically import them. You can extend alembic_helpers.render_item to handle this.
Create a custom render_item function that calls the default alembic_helpers.render_item first, then checks for your custom type to inject the necessary import statement into autogen_context.imports.
from geoalchemy2 import alembic_helpers
from my_package.custom_types import TheCustomType
def render_item(obj_type, obj, autogen_context):
# Try default spatial rendering first
spatial_type = alembic_helpers.render_item(obj_type, obj, autogen_context)
if spatial_type:
return spatial_type
# Handle custom type imports
if obj_type == 'type' and isinstance(obj, TheCustomType):
import_name = obj.__class__.__name__
autogen_context.imports.add(f"from my_package.custom_types import {import_name}")
return "%r" % obj
return False
# Use this in context.configure(render_item=render_item, ...)Certain spatial functions (such as ST_Transform(), ST_Union(), and ST_SnapToGrid()) are compatible with both Geometry and Raster types.
Because GeoAlchemy2 defines these functions on the Geometry type to avoid ambiguity, you must explicitly specify the type when using them on a Raster column. Pass the type_=Raster argument to the function call to enforce the correct type.
from geoalchemy2.types import Raster
# Use ST_Transform on a Raster column by specifying type_
query = session.query(Lake.raster.ST_Transform(2154, type_=Raster))