Skip to main content

gpkglite

Read, write and validate OGC GeoPackage files in pure Python. No GDAL, no compiled dependencies, standard library only (sqlite3, struct, json).

CI Python License

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 .gpkg without 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(): every gpkg_contents entry (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_sys records, including the WKT2 definition_12_063 column when the CRS WKT extension is present.
  • layer.features(...) yields GeoJSON-like Feature dicts; layer.rows(...) yields flat dicts. Both accept:
    • where + params: an SQL boolean expression with ? or :name placeholders,
    • bbox=(minx, miny, maxx, maxy): uses the rtree_<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 UnsupportedGeometryError with a readable message. Their raw WKB is still available with geometry_format="wkb", and their header envelope still drives bbox filters.
  • 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 required gpkg_spatial_ref_sys rows (-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) and gpkg.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/datetime values are stored as ISO 8601 UTC text, bool as 0/1, dict/list as JSON text.
  • The gpkg_contents extent grows on every write; layer.update_extent() recomputes it.
  • RTree spatial index (rtree_<t>_<c>) with the standard triggers, registered in gpkg_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_IsEmpty and ST_SRID are 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, by ogrinfo and GDAL's validate_gpkg.py;
  • the committed fixtures in tests/data were written by ogr2ogr (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.
  • where and order_by are inserted into SQL verbatim; never build them from untrusted input. Bind values through params.
  • 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.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gpkglite 0.1.0
File Size Uploaded Provenance Actions
gpkglite-0.1.0.tar.gz 85.6 kB With provenance Details for gpkglite-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for gpkglite 0.1.0
File Interpreter ABI Platform Actions
gpkglite-0.1.0-py3-none-any.whl Python 3 none any Details for gpkglite-0.1.0-py3-none-any.whl

Total release size: 140.6 kB

Release files / gpkglite-0.1.0.tar.gz

Download URL gpkglite-0.1.0.tar.gz
Size 85.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6fc269401079e140b75d49eb502053d1ced2244fcefa547bb2623f598a59dc93
BLAKE2b-256 checksum
How to use checksums
1a6f56536cb5d6d1265a8829ed480552fcee73af3352aa3da37417fd309d03cc
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 log

Release files / gpkglite-0.1.0-py3-none-any.whl

Download URL gpkglite-0.1.0-py3-none-any.whl
Size 55.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3880426ef02d222310c002ffaa29b4c867818562bc380266558431861c923b9a
BLAKE2b-256 checksum
How to use checksums
e3070f14c71141a189bc7463d42ff526c9f7a5a1180001f62fcb7977535ffe9f
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 log

Security for gpkglite 0.1.0

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.

Select from 2 files

Attestation for gpkglite-0.1.0.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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page