geopackage 0.1.2

Read and write OGC GeoPackage (.gpkg) files: pure-Rust container handling over bundled SQLite, with spec-correct spatial indexing
Documentation

geopackage

CI crates.io docs.rs

A fast, robust, production-quality Rust implementation of the OGC GeoPackage 1.4 format, intended for use from Rust and, via a C ABI with the Arrow C Data Interface as the bulk data plane, from higher-level languages.

Status: pre-alpha (0.1.0). The read and write paths are complete and validated against external tooling (see Conformance), but the API will change without notice before 1.0.

Install

[dependencies]
geopackage = "0.1"
geo-types = "0.7"  # any geo-traits implementation works; this is the common one

SQLite is bundled and built from source, so a C compiler is required; there is no system SQLite dependency. The minimum supported Rust version is 1.95.

Example

Create a file, declare a point layer, write features, index it, and query by bounding box:

use geo_types::Point;
use geopackage::core::types::{ColumnType, GeometryType};
use geopackage::{
    BoundingBox, ColumnSpec, GeoPackage, GeometrySpec, NewFeature, TableSchemaBuilder, Value,
};

let gpkg = GeoPackage::create("cities.gpkg")?;

gpkg.create_layer(
    &TableSchemaBuilder::new("cities")
        .column(ColumnSpec::new("name", ColumnType::Text(None)))
        .geometry(GeometrySpec::new(GeometryType::Point, 4326)),
)?;

let layer = gpkg.layer("cities")?;
layer.create_spatial_index()?;

layer.write_all(
    vec![
        NewFeature::new(Point::new(-6.26, 53.35), vec![Value::Text("Dublin".into())]),
        NewFeature::new(Point::new(-0.13, 51.51), vec![Value::Text("London".into())]),
    ],
    1000,
)?;

// Uses the RTree index when one is present, a full scan otherwise.
for feature in layer.features_in(BoundingBox::new(-7.0, 53.0, -6.0, 54.0))? {
    println!("{:?}", feature?.value("name"));
}

More examples

Runnable programs in geopackage/examples:

Example What it shows
quickstart The snippet above, kept compiling.
inspect Layers, schemas, SRS, feature counts and spatial-index health for a file, in the manner of ogrinfo -al -so. Uses open_lenient, so it reports problems rather than refusing to open.
bulk_load Loading a large point layer with write_all, creating the index first so the bulk shadow-table build is used.
bbox_query features_in bounding-box queries (RTree-accelerated or full-scan) and the select WHERE passthrough, with lazy geometry parsing.
repair_index Detecting Legacy and Stale spatial indexes and repairing them.
cargo run --release --example bulk_load -- 200000 out.gpkg
cargo run --example inspect -- out.gpkg
cargo run --example bbox_query -- out.gpkg points -10 -5 10 5

Workspace

Crate Purpose
geopackage-core No-IO spec layer: GeoPackage Binary (GPB) header codec, normative table DDL, version-aware RTree trigger SQL, identifier quoting, application_id/user_version handling. Dependency-light by design so other implementations can share it.
geopackage The library: container create/open over rusqlite (bundled + functions), the feature/attribute read and write paths, the RTree spatial-index lifecycle, and registration of the ST_IsEmpty/ST_MinX/… SQL functions required by the spatial index triggers.
geopackage-core/fuzz cargo-fuzz targets (GPB parser).

Design notes

  • Sync core on rusqlite. The RTree extension's triggers call ST_* functions that must be registered on every writing connection; sqlx-sqlite cannot register custom functions, and SQLite is synchronous anyway. Async wrappers can sit on top.
  • GeoPackage 1.4 trigger set (update5/update6/update7) is emitted for new indexes; older generations are detected and repairable (repair_spatial_index) rather than silently mixed - mixed-generation triggers are a known source of file corruption (e.g. UPSERT against pre-1.4 triggers).
  • Escape hatches everywhere: GeoPackage::connection() / from_connection() expose the underlying rusqlite connection. SQLite is the query engine; we do not wrap what we do not need to.
  • Interchange-first close. WAL is opt-in, and a handle that opted into it checkpoints and resets the file to DELETE on close, so a handed-over .gpkg is a single file with no sidecars.

Conformance

Files written by this crate are checked against OGC ets-gpkg12 (40 passed, 1 failure whose regex hard-codes the GeoPackage 1.2 trigger set and rejects a correct 1.4 one; no 1.3/1.4 ETS exists), the PDOK validator (clean but for two advisory findings on deliberate choices), ogrinfo, and a GDAL round-trip that byte-compares geometry WKB and attribute values. The test corpus includes GDAL-written, QGIS-written and raw-SQLite files. See roadmap/04-m2-write-rtree.md for the detailed results.

Known limitations

  • Untrusted files can trigger a large allocation. The wkb 0.9.2 reader pre-allocates from element counts read out of the geometry blob without bounding them against the buffer, so a malformed 17-byte GPB blob declaring a 0xFFFFFFFF-member collection drives a multi-gigabyte allocation. Found by the gpb_geometry fuzz target. The fix belongs upstream in georust/wkb; do not parse untrusted GeoPackage files with 0.1.0. Tracked in #3.
  • Non-linear curve types (CIRCULARSTRING, COMPOUNDCURVE, …) cannot have their envelopes computed and so cannot be inserted into an indexed table. Tracked in #5.
  • Feature iteration materialises the result set rather than streaming. Tracked in #4.

Roadmap

M1 (feature and attribute read: scan, bbox via rtree, WHERE passthrough, full WKB envelopes) and M2 (write path, layer creation, bulk rtree build, trigger repair) are complete and released as v0.1. Next: M3 GeoArrow RecordBatch I/O, C ABI (geopackage-ffi), CLI, for v0.2. M4: tiles. M5: extensions (CRS WKT2, metadata, schema, related tables).

The full roadmap, including the decision record, lives in roadmap/.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.