geopackage-toolkit
Python toolkit for GeoPackage spatial data. Validate, query, and operate on spatial data without PostGIS or ArcGIS.
Zero compiled dependencies. Uses SpatiaLite (embedded in SQLite) for all spatial operations. One file format (.gpkg), one dependency chain, clean Python API.
from geopkgtoolkit import connect, validate_layers, count_in_zones
# Validate geometry health
report = validate_layers("data.gpkg", expected_srid=4326)
print(report.summary())
# Count buildings per administrative zone
con = connect("data.gpkg")
counts = count_in_zones(con, "buildings", "admin3")
for zone_id, count in counts[:5]:
print(f"Zone {zone_id}: {count} buildings")
Why
GeoPackage is the OGC standard that replaces Shapefile. It's SQLite-based, supports vector + raster + tiles, and works everywhere. But the tooling around it is fractured:
ogr2ogrworks but isn't PythonicGeoPandasadds heavy compiled dependenciesSpatiaLitehas Windows quirks nobody documents- No single package covers validate-query-convert-publish
This toolkit fills that gap. Local-first, no server required.
Installation
pip install geopackage-toolkit
Prerequisites
SpatiaLite must be installed on your system:
| OS | Install |
|---|---|
| Windows | Download from gaia-gis.it and set SPATIALITE_DIR env var |
| Ubuntu/Debian | sudo apt install libspatialite-dev |
| macOS | brew install libspatialite |
| Conda | conda install -c conda-forge libspatialite |
CLI
# Validate geometry health
geopkg validate data.gpkg --srid 4326
# Count features per zone
geopkg count data.gpkg --features buildings --zones admin3
# Show layer info
geopkg info data.gpkg
# Buffer features (output saved as new layer in same GeoPackage)
geopkg buffer data.gpkg --layer buildings --distance 100 --output buffered_buildings
# Clip a layer to a boundary
geopkg clip data.gpkg --source buildings --clip district_boundary --output clipped_buildings
# Spatial intersection of two layers
geopkg intersect data.gpkg --layer-a buildings --layer-b flood_zones --output buildings_in_flood
# Export a layer to GeoJSON (pure Python, no extra dependencies)
geopkg export data.gpkg --layer buildings --format geojson --output buildings.geojson
# Export a layer to Shapefile (requires: pip install geopackage-toolkit[convert])
geopkg export data.gpkg --layer buildings --format shapefile --output buildings.shp
# Import GeoJSON into a GeoPackage (creates the file if missing)
geopkg import buildings.geojson --output new_data.gpkg --layer buildings
# Import a Shapefile into a GeoPackage
geopkg import buildings.shp --output new_data.gpkg --layer buildings
Python API
Validate
from geopkgtoolkit import validate_layers, validate_layer
# Validate all layers
report = validate_layers("data.gpkg", expected_srid=4326)
print(report.summary())
# GeoPackage: data.gpkg
# 9 layers, 685,968 features, 0 warnings
# OSM_Buildings: 515,013 features, SRID=4326, OK
# lbn_adm3: 1,627 features, SRID=4326, OK
# ...
assert report.is_valid # True if no warnings
# Validate a single layer
con = validate_layer(con, "buildings", expected_srid=4326)
Checks: null geometries, empty geometries, invalid geometries (self-intersections), SRID mismatches, bounding box sanity.
Query
from geopkgtoolkit import connect, count_in_zones, bbox_filter
con = connect("data.gpkg")
# Count features per zone (rtree-accelerated)
counts = count_in_zones(con, "buildings", "admin3")
# Returns: [(zone_fid, count), ...]
# Bounding box filter (rtree-accelerated)
fids = bbox_filter(con, "buildings", (35.4, 33.8, 35.6, 33.9))
# Returns: [fid, ...]
# Point-in-polygon classification
from geopkgtoolkit import points_in_polygons
result = points_in_polygons(con, "pois", "districts")
# Returns: [(point_fid, polygon_fid_or_None), ...]
Spatial Operations
from geopkgtoolkit import connect, buffer, clip, intersect
con = connect("data.gpkg")
# Buffer features by a distance (CRS units)
buffered = buffer(con, "buildings", distance=100, output_table="buffered_buildings")
# Creates new layer with buffered polygons
# Clip features to a boundary
clipped = clip(con, "buildings", "district_boundary", output_table="clipped_buildings")
# Creates new layer with features clipped to polygon boundary
# Spatial intersection of two layers
result = intersect(con, "buildings", "flood_zones", output_table="buildings_in_flood")
# Creates new layer with the intersection of two layers
All operations write results back to the same GeoPackage file. Attributes from both layers are preserved in intersection.
Format Conversion
from geopkgtoolkit import connect, export_geojson, export_shapefile, import_geojson, import_shapefile
con = connect("data.gpkg")
# Export to GeoJSON (pure Python, zero extra dependencies)
export_geojson(con, "buildings", "buildings.geojson")
# Export to Shapefile (requires: pip install geopackage-toolkit[convert])
export_shapefile(con, "buildings", "buildings.shp")
con.close()
# Import GeoJSON (creates the GeoPackage if it doesn't exist)
import_geojson("new_data.gpkg", "input.geojson", "buildings")
# Import Shapefile
import_shapefile("new_data.gpkg", "input.shp", "buildings")
GeoJSON export/import is pure Python. Shapefile support requires the optional pyshp dependency: pip install geopackage-toolkit[convert].
Connect
from geopkgtoolkit import connect
con = connect("data.gpkg") # Auto-loads SpatiaLite
layers = con.execute("SELECT table_name FROM gpkg_geometry_columns").fetchall()
con.close()
How It Works
All spatial operations use SpatiaLite (embedded spatial SQL extension for SQLite) with GeoPackage's native rtree tables for spatial indexing. This means:
- No server process (unlike PostGIS)
- No compiled Python bindings (unlike GeoPandas/GDAL wheels)
- Single
.gpkgfile per dataset - Works on Windows, Linux, macOS
Performance
| Operation | Method | 500k features x 1k zones |
|---|---|---|
count_in_zones |
rtree bbox + ST_Contains | ~8 seconds |
count_in_zones (no index) |
ST_Contains only | >10 minutes |
bbox_filter |
rtree lookup | <1 second |
The toolkit auto-creates rtree indexes when missing. First run builds the index, subsequent runs use it.
Roadmap
- v0.1.0: Validate + Query modules
- v0.2.0: Spatial operations (buffer, clip, intersect) with CLI commands
- v0.3.0: Format conversion (Shapefile, GeoJSON, FlatGeobuf to/from GeoPackage)
- v0.4.0: Config-driven batch pipeline
- v0.5.0: Vector tile generation for web publishing
- v1.0.0: Full toolkit with visualization and schema extraction
Contributing
Contributions welcome. Please open an issue first to discuss what you'd like to change.
License
MIT
Acknowledgments
Built on top of SpatiaLite by Alessandro Furieri and the GeoPackage OGC standard.
Release files for geopackage-toolkit 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| geopackage_toolkit-0.3.0.tar.gz | 45.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| geopackage_toolkit-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.1 kB
Release files / geopackage_toolkit-0.3.0.tar.gz
| Download URL | geopackage_toolkit-0.3.0.tar.gz |
|---|---|
| Size | 45.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f500e1c3e140aff07e40d9ded98491d5b3bd37f5fb21bfa4b86d3ddad57a3a27
|
|
BLAKE2b-256 checksum How to use checksums |
cc1d4e843bbbe37303a74a1c647f9005cf5d6b31236f14dd86245dd64ee15b80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Release files / geopackage_toolkit-0.3.0-py3-none-any.whl
| Download URL | geopackage_toolkit-0.3.0-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a51214a9a056bd63698a531ff4eeaf96759f240053af2696467d38f7169477b4
|
|
BLAKE2b-256 checksum How to use checksums |
dfc584b731b3723906fac0aadfd1f54e6c07687ae81133c70efc096ac3f1c16a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|