gpkglite
Read, write and validate OGC GeoPackage files in pure Python. No GDAL, no compiled
dependencies, standard library only (sqlite3, struct, json).
GeoPackage (.gpkg) is the OGC standard container for vector data: an SQLite database
with a few well-defined tables and a binary geometry encoding. Reading or writing one
usually means installing GDAL (via fiona, pyogrio, geopandas or osgeo). That is
heavy, or impossible, in:
- serverless functions (AWS Lambda, Cloud Functions) with tight package size limits,
- slim Docker images and CI jobs,
- Pyodide / JupyterLite in the browser,
- Windows machines without OSGeo4W or conda,
- libraries that want to accept a
.gpkgwithout pulling GDAL into their dependency tree.
gpkglite is a small, typed, dependency-free implementation of the parts of
OGC 12-128r18 (GeoPackage 1.3.1) that vector
workflows need. Files it writes open in GDAL/OGR, QGIS, geopandas/pyogrio and fiona,
and it reads files written by them.
Installation
pip install gpkglite # standard library only
pip install "gpkglite[geo]" # + shapely / geopandas interop helpers
Quick start
import gpkglite
# write
with gpkglite.create("cities.gpkg") as gpkg:
layer = gpkg.create_layer(
"cities", "POINT", srs_id=4326,
fields={"name": "TEXT", "population": "INTEGER", "capital": "BOOLEAN"},
)
layer.insert({"type": "Point", "coordinates": [28.97, 41.01]},
{"name": "Istanbul", "population": 15_600_000, "capital": False})
layer.insert({"type": "Point", "coordinates": [32.85, 39.93]},
{"name": "Ankara", "population": 5_700_000, "capital": True})
# read
with gpkglite.open("cities.gpkg") as gpkg:
for info in gpkg.layers():
print(info.name, info.geometry_type, info.srs_id, info.bounds)
cities = gpkg["cities"]
print(cities.fields) # {'name': 'TEXT', 'population': 'INTEGER', 'capital': 'BOOLEAN'}
print(cities.crs.authority) # 'EPSG:4326'
for feature in cities.features(where="population > ?", params=[10_000_000]):
print(feature["properties"]["name"], feature["geometry"]["coordinates"])
# check it
report = gpkglite.validate("cities.gpkg")
print(report) # cities.gpkg: valid GeoPackage 1.3.1 (0 errors, ...)
Features
Reading
gpkg.layers(): everygpkg_contentsentry (features and attribute tables) with geometry column, geometry type, Z/M flags, SRS id and extent.layer.columns,layer.fields,layer.schema: column names and GeoPackage types.gpkg.srs(id),gpkg.list_srs(),layer.crs:gpkg_spatial_ref_sysrecords, including the WKT2definition_12_063column when the CRS WKT extension is present.layer.features(...)yields GeoJSON-likeFeaturedicts;layer.rows(...)yields flat dicts. Both accept:where+params: an SQL boolean expression with?or:nameplaceholders,bbox=(minx, miny, maxx, maxy): uses thertree_<table>_<column>index when present, followed by an exact envelope test,limit,offset,order_by,columns,geometry_format="geojson" | "wkb" | "gpkg" | "shapely",on_error="raise" | "null" | "skip"for geometries gpkglite cannot decode.
layer.count(...),layer.get(fid),layer.compute_bounds(),layer.to_geojson().
Geometry encoding
- StandardGeoPackageBinary headers: magic, version, flags, both byte orders, all envelope types (0 none, 1 XY, 2 XYZ, 3 XYM, 4 XYZM), the empty-geometry flag, SRS id.
- ISO WKB (and PostGIS extended WKB on input) for Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon and GeometryCollection, in XY, XYZ, XYM and XYZM, either byte order, nested collections, empty geometries (NaN points).
- Curve and surface types (CircularString, CompoundCurve, CurvePolygon, MultiCurve,
MultiSurface, PolyhedralSurface, TIN, Triangle) and ExtendedGeoPackageBinary raise
UnsupportedGeometryErrorwith a readable message. Their raw WKB is still available withgeometry_format="wkb", and their header envelope still drivesbboxfilters. - Low-level API:
gpkglite.wkb.loads/dumps,gpkglite.encode_geometry,gpkglite.decode_geometry,gpkglite.wkt.dumps.
Geometries are plain dictionaries. Positions have 2, 3 or 4 numbers; a measured geometry
carries "dims": "XYM" (or "XYZM") so it can be told apart from XYZ:
{"type": "LineString", "coordinates": [[0, 0, 12.5], [10, 0, 13.0]]} # XYZ
{"type": "Point", "coordinates": [1, 2, 0.75], "dims": "XYM"} # XYM
Any object with __geo_interface__ (shapely, geojson, ...) is accepted as input.
Writing
gpkglite.create(path):application_id = 'GPKG',user_version = 10301, the requiredgpkg_spatial_ref_sysrows (-1, 0, EPSG:4326),gpkg_contents,gpkg_geometry_columns.create(path, wkt2=True)adds the CRS WKT extension.gpkg.add_srs(...): built-in definitions for EPSG 4326, 3857, 4258, 4269 and all WGS 84 / UTM zones (32601-32660, 32701-32760); any other CRS with an explicit WKT.gpkg.create_layer(name, geometry_type, srs_id, fields, z=0|1|2, m=0|1|2, spatial_index=True)andgpkg.create_table(name, fields)for attribute-only tables. Field types:BOOLEAN, TINYINT, SMALLINT, MEDIUMINT, INT, INTEGER, FLOAT, DOUBLE, REAL, TEXT, TEXT(n), BLOB, BLOB(n), DATE, DATETIME, or Python types (int,str,bool,float,bytes,datetime.date,datetime.datetime).layer.insert(...),layer.write_features(...)(one transaction),layer.update(...),layer.delete(fid),layer.delete_where(...),layer.add_field(...). Geometry type and Z/M constraints of the layer are enforced;date/datetimevalues are stored as ISO 8601 UTC text,boolas 0/1,dict/listas JSON text.- The
gpkg_contentsextent grows on every write;layer.update_extent()recomputes it. - RTree spatial index (
rtree_<t>_<c>) with the standard triggers, registered ingpkg_extensions. GeoPackage 1.3 trigger set for new files; the 1.4 set (update5-update7) when extending a 1.4 file written by GDAL.ST_MinX/MaxX/MinY/MaxY,ST_IsEmptyandST_SRIDare registered on every connection so these triggers work from Python. - Metadata extension:
gpkg.add_metadata(...),gpkg.metadata(). gpkg.transaction()groups several operations atomically.
Validation
gpkglite.validate(path) returns a ValidationReport (report.ok, report.errors,
report.warnings, report.as_dict()), with requirement numbers from the specification:
- SQLite header,
application_id,user_version,integrity_check,foreign_key_check; - structure of
gpkg_spatial_ref_sys,gpkg_contents,gpkg_geometry_columns,gpkg_extensions,gpkg_metadata,gpkg_metadata_reference; mandatory SRS rows; - references between contents, geometry columns and SRS; timestamps; column types; integer primary keys;
- every geometry blob: header, envelope code, ExtendedGeoPackageBinary, SRS id, empty flag, WKB, declared geometry type, Z/M constraints, envelope matching the coordinates;
- the RTree extension: registration, columns, triggers for the file's version, and that the index holds exactly the non-empty geometries.
Conversion helpers
gpkglite.from_geojson("roads.geojson", "out.gpkg") # layer "roads"
gpkglite.from_csv("stations.csv", "out.gpkg", x="lon", y="lat") # POINT layer
doc = gpkglite.to_geojson("out.gpkg", "roads", "roads.geojson")
text = gpkglite.to_csv("out.gpkg", "stations", geometry="xy")
# with the [geo] extra
gdf = gpkglite.to_geodataframe("out.gpkg", "roads")
gpkglite.from_geodataframe(gdf.to_crs(32635), "out.gpkg", "roads_utm")
from_geojson infers the schema and geometry type (mixing Polygon and MultiPolygon
gives a MULTIPOLYGON layer) and honours the legacy crs member.
Command line
gpkglite info data.gpkg # layers, schema, CRS, extent, extensions (--json)
gpkglite dump data.gpkg --layer roads --format geojson
gpkglite dump data.gpkg -l roads -f csv --where "type = 'primary'" --bbox 29,41,29.1,41.1
gpkglite dump data.gpkg -l roads -f jsonl --limit 100 --offset 200 -o page3.jsonl
gpkglite validate data.gpkg # exit status 1 when invalid (--json)
gpkglite from-geojson in.geojson -o out.gpkg [--layer name] [--srs-id 3857]
gpkglite from-csv points.csv -o out.gpkg -x lon -y lat
Interoperability
The test-suite cross-checks with GDAL:
- files written by gpkglite are read by
pyogrio(GDAL 3.12 wheel) and, when available, byogrinfoand GDAL'svalidate_gpkg.py; - the committed fixtures in
tests/datawere written byogr2ogr(GDAL 3.13, GeoPackage 1.4) and cover all geometry types, Z/M, empty and NULL geometries, DATE/DATETIME/BOOLEAN fields and curves; gpkglite reads them and writes into them through GDAL's 1.4 RTree triggers; - QGIS 3 (OGR provider) loads every layer type gpkglite writes.
Scope and limitations
- Vector features and attribute tables only: no tiles, gridded coverage, or related tables extensions.
- Curve geometries are recognised but not decoded.
- No coordinate transformation: coordinates are stored in the layer CRS as given.
whereandorder_byare inserted into SQL verbatim; never build them from untrusted input. Bind values throughparams.- SQLite must provide the R*Tree module for spatial indexes (true for CPython on Windows, macOS and mainstream Linux distributions, and for Pyodide).
Development
pip install -e ".[dev,geo]"
pytest --cov=gpkglite
ruff check . && ruff format --check src tests && mypy src
Set GPKGLITE_OGRINFO (path to ogrinfo) and GPKGLITE_VALIDATE_GPKG (command that runs
GDAL's validate_gpkg.py) to enable the optional GDAL cross-validation tests.
License
MIT, see LICENSE. Citation metadata is in CITATION.cff.
Metadata
Release files for gpkglite 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gpkglite-0.1.1.tar.gz | 85.6 kB | With provenance | Details for gpkglite-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gpkglite-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for gpkglite-0.1.1-py3-none-any.whl |
Total release size: 140.7 kB
Release files / gpkglite-0.1.1.tar.gz
| Download URL | gpkglite-0.1.1.tar.gz |
|---|---|
| Size | 85.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
da971a3b8c50ee359cc00296b3277e71e0f4965e7e0158a9486aa8049e751208
|
|
BLAKE2b-256 checksum How to use checksums |
395f0f21aed4692b98578afdf78081d1f53623472d007d38eb10760a8fdab61a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / gpkglite-0.1.1-py3-none-any.whl
| Download URL | gpkglite-0.1.1-py3-none-any.whl |
|---|---|
| Size | 55.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
997feaf0a947161c34fed8c77ac590fb3b4cbc6f0009f7ceea0ace19b4907c5e
|
|
BLAKE2b-256 checksum How to use checksums |
cb986399a38cf5016b57c95d95a2e86c622336e94d196e2eb63c85a5f1b2db67
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for gpkglite 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for gpkglite-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.