GeoAlchemy 2
GeoAlchemy 2 is a Python library that extends SQLAlchemy to facilitate working with spatial databases. It primarily focuses on PostGIS, offering robust support for geometry, geography, and raster types, while also providing support for SpatiaLite, MySQL, MariaDB, and GeoPackage. It seamlessly integrates with both SQLAlchemy's Object Relational Mapper (ORM) and its SQL Expression Language, enabling users to define spatial columns, leverage spatial functions, and perform spatial operations. The library is actively maintained, with its current version being 0.18.4, and exhibits a regular release cadence.
Warnings
- breaking GeoAlchemy 2 dropped support for Python versions older than 3.10 starting from version 0.18.0. Ensure your Python environment meets this requirement.
- breaking Migrating from GeoAlchemy 1 to GeoAlchemy 2 involves significant API changes. Notably, specific geometry types like `Point` are replaced by `Geometry(geometry_type='POINT')`, and spatial functions are accessed via SQLAlchemy's `func` object instead of a dedicated `geoalchemy.functions` namespace.
- gotcha When querying geometry columns, GeoAlchemy 2 returns `WKBElement` objects by default, which are binary representations (EWKB). To get human-readable formats like WKT or GeoJSON directly from the database, use spatial functions like `func.ST_AsText()` or `func.ST_AsGeoJSON()` in your queries. For in-application processing, convert `WKBElement` to Shapely geometries using `geoalchemy2.shape.to_shape()` (requires `Shapely`).
- gotcha Shapely is an optional dependency for GeoAlchemy 2. If you plan to use functions like `to_shape` or `from_shape` for integrating with Shapely geometries, you must install it separately using `pip install geoalchemy2[shapely]`. Recent versions (0.18.3, 0.18.4) specifically addressed `Shapely` import fixes.
Install
-
pip install geoalchemy2 -
pip install geoalchemy2[shapely]
Imports
- Geometry
from geoalchemy2 import Geometry
- WKTElement
from geoalchemy2 import WKTElement
- func
from sqlalchemy import func
- ST_AsText
from geoalchemy2.functions import ST_AsText
- to_shape
from geoalchemy2.shape import to_shape
Quickstart
import os
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import func
from geoalchemy2 import Geometry, WKTElement
# Ensure you have a PostGIS-enabled database URL set in your environment
# Example: 'postgresql+psycopg2://user:password@host:port/dbname'
DATABASE_URL = os.environ.get('GEOALCHEMY_DATABASE_URL', 'postgresql+psycopg2://gis:gis@localhost:5432/gis_test')
Base = declarative_base()
class City(Base):
__tablename__ = 'cities'
id = Column(Integer, primary_key=True)
name = Column(String)
# Define a geometry column for points, SRID 4326 (WGS 84 Lat/Lon)
geom = Column(Geometry(geometry_type='POINT', srid=4326))
def __repr__(self):
return f"<City(name='{self.name}', geom='{self.geom}')>"
# Create an engine and include the geoalchemy2 plugin
engine = create_engine(DATABASE_URL, echo=False, plugins=["geoalchemy2"])
# Create all tables in the database
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
try:
# Add a new city with a point geometry using WKTElement
new_york = City(
name='New York',
geom=WKTElement('POINT(-74.0060 40.7128)', srid=4326)
)
session.add(new_york)
session.commit()
print(f"Added: {new_york.name} with geometry {new_york.geom}")
# Query the city and retrieve its geometry as WKT
retrieved_city = session.query(City).filter_by(name='New York').one()
wkt_geom = session.scalar(func.ST_AsText(retrieved_city.geom))
print(f"Retrieved {retrieved_city.name}. Geometry in WKT: {wkt_geom}")
# Example of a spatial query: find cities containing a point
# (This assumes a larger dataset or more complex geometry for a meaningful result)
point_to_check = WKTElement('POINT(-74.0060 40.7128)', srid=4326)
cities_containing_point = session.query(City).filter(
func.ST_Contains(retrieved_city.geom, point_to_check)
).all()
print(f"Cities containing point {point_to_check.data}: {[c.name for c in cities_containing_point]}")
except Exception as e:
session.rollback()
print(f"An error occurred: {e}")
finally:
session.close()
# Clean up (optional, for idempotent quickstart)
Base.metadata.drop_all(engine)