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.
Common errors
-
ModuleNotFoundError: No module named 'geoalchemy2'
cause The `geoalchemy2` library is not installed in the Python environment being used, or the environment is not activated.fixEnsure `geoalchemy2` is installed in your active virtual environment by running: `pip install geoalchemy2` -
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedFunction) function st_asewkb(point) does not exist
cause This error typically indicates that the PostGIS extension has not been enabled in your PostgreSQL database, preventing the database from recognizing spatial functions like `ST_AsEWKB` or `ST_GeomFromEWKT`.fixConnect to your PostgreSQL database (e.g., via `psql`) and enable the PostGIS extension: `CREATE EXTENSION postgis;` -
The migration script misses the relevant imports from geoalchemy2
cause When using Alembic with `geoalchemy2` and `--autogenerate`, Alembic may not automatically add the necessary `from geoalchemy2 import Geometry` (or other spatial types) imports to the generated migration script, leading to `NameError` when applying the migration.fixManually add `from geoalchemy2 import Geometry` (or the specific spatial type being used, like `Geography`, `Raster`) to your Alembic migration script. You might also need to adjust `render_item` in `env.py` for custom types. -
AttributeError: 'Function' object has no attribute 'items'
cause This error often occurs when trying to directly serialize SQLAlchemy `func` objects or `WKBElement` instances (which represent spatial data) to JSON using libraries like `simplejson` or `json.dumps` without a proper custom encoder.fixConvert `WKBElement` objects to a serializable format (e.g., WKT or GeoJSON string) before JSON serialization, often using `func.ST_AsGeoJSON()` or `WKBElement.desc` to get WKT. For `simplejson`, disabling `namedtuple_as_object` and `for_json` options or providing a custom `default` handler can help. -
Geometry has Z dimension but column does not
cause You are attempting to insert or create a table with 3D geometry data (e.g., `LINESTRING Z`) into a database column that is defined for 2D geometries only, or vice-versa, causing a dimension mismatch.fixEnsure that the `dimension` parameter in your `geoalchemy2.types.Geometry` (or `Geography`) definition matches the dimensionality of the data you intend to store. For 3D data, specify `dimension=3` (e.g., `Column(Geometry(geometry_type='POINTZ', dimension=3))`). If the table is already created, you might need to alter the column type or create a new column with the correct dimension.
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.types import Geometry
from geoalchemy2 import Geometry
- WKTElement
from geoalchemy2.elements import 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)