Contents

outdooractive/gis-tools-geopackage

OGC GeoPackage (.gpkg) read and write support for Swift, built on top of gis-tools.

Features

  • Feature tables — read/write vector features with full geometry and attribute support
  • Spatial index (rtree) — accelerated bounding-box queries via gpkg_rtree_index
  • Tile pyramids — read/write raster tile blobs (PNG/JPEG/WebP) at multiple zoom levels
  • Attribute tables — non-spatial data with data_type = "attributes"
  • Media tables — blob storage linked to features (Related Tables Extension)
  • Relationshipsgpkgext_relations for linking feature, attribute, and media tables
  • Schema validationGeoPackage.validate(url:) checks spec compliance
  • Async streamingFeatureStream yields features one at a time via AsyncSequence
  • Thread-safe connectionGeoPackageConnection actor with paginated reads and incremental writes

Requirements

Swift 6.1 or higher. Compiles on iOS (≥ iOS 15), macOS (≥ macOS 15), tvOS (≥ tvOS 15), watchOS (≥ watchOS 7), Linux, Android and Wasm. Requires libsqlite3 (available by default on all supported platforms).

Installation with Swift Package Manager

dependencies: [
    .package(url: "https://github.com/Outdooractive/gis-tools-geopackage", from: "1.0.0"),
    .package(url: "https://github.com/Outdooractive/gis-tools", from: "2.0.0"),
],
targets: [
    .target(name: "MyTarget", dependencies: [
        .product(name: "GISToolsGeoPackage", package: "gis-tools-geopackage"),
        .product(name: "GISTools", package: "gis-tools"),
    ]),
]

Usage

Feature tables (convenience API)

import GISTools
import GISToolsGeoPackage

// Read all features:
let fc = try await FeatureCollection(geopackage: url, table: "features")

// Write features:
try await fc.writeGeopackage(to: url, table: "features")

Feature tables (connection-based API)

let gpkg = try await GeoPackageConnection(url: url)
defer { await gpkg.close() }

// Paginated read:
let page = try await gpkg.readFeatures(table: "countries", limit: 100, offset: 0)

// Async stream:
for try await feature in gpkg.features(table: "countries") {
    // process one at a time
}

// Incremental write:
try await gpkg.createMetadata()
try await gpkg.write(features: batch1, to: "places")
try await gpkg.write(features: batch2, to: "places")

Bounding-box queries

let bbox = BoundingBox(
    southWest: Coordinate3D(latitude: 35.0, longitude: -10.0),
    northEast: Coordinate3D(latitude: 60.0, longitude: 40.0))
let filtered = try await FeatureCollection(
    geopackage: url, table: "countries",
    boundingBox: bbox)

Tile pyramids

// Read a single tile:
let data = try await gpkg.readTile(
    for: TileKey(zoom: 5, column: 12, row: 8),
    from: "tiles")

// Write a tile pyramid:
try await gpkg.writeTilePyramid(
    tiles: tileData, to: "tiles",
    matrixSetBounds: bounds, matrices: matrices)

Attribute and media tables

// Read a non-spatial attribute table:
let attrs = try await gpkg.readAttributeTable(table: "station_info", rowId: 42)

// Read media rows:
let media = try await gpkg.readMediaRows(table: "photos", rowId: 1)

Validation

let result = try GeoPackage.validate(url: url)
if result.isValid { /* conforms to spec */ }

Relationships

When features are loaded from a GeoPackage, the row ID and table name are stored in foreignMembers:

let feature = fc.features[0]
print(feature.gpkgRowId)       // Int?
print(feature.gpkgTableName)   // String?

// Look up related data:
let related = try await feature.relatedAttributes(in: gpkg)
let media = try await feature.relatedMedia(in: gpkg)

Feature identifiers

GeoPackage feature tables distinguish between two kinds of identifier. This library handles both:

1. The GeoJSON Feature.id (the id column)

The OGC GeoPackage spec requires every feature table to have an INTEGER PRIMARY KEY column that acts as a rowid alias. GeoJSON Feature.id values, however, can be strings, integers, or doubles — not necessarily numeric. This library therefore writes two columns:

  • fidINTEGER PRIMARY KEY AUTOINCREMENT. This is the spec-mandated primary key; it is auto-assigned by SQLite and never derived from Feature.id.
  • id — stores the GeoJSON Feature.id. Its SQLite type is inferred from the identifiers in the collection so that uniform collections round-trip with type fidelity:

| Feature.id values in the collection | id column type | Read-back Feature.id | |---|---|---| | all .int / .uint | INTEGER | .int | | all .double | REAL | .double | | all .string | TEXT | .string | | mixed types (or none) | TEXT | .string (fallback) |

Mixed-type collections fall back to TEXT because no single SQLite type can hold every value; integer ids in such a collection come back as .string (e.g. .int(42).string("42")).

2. The GeoPackage row id (gpkgRowId)

feature.gpkgRowId reflects the fid INTEGER primary key (the SQLite rowid), not the GeoJSON Feature.id. It is stored in foreignMembers (key _gpkg_rowid) and is set automatically by the reader. It is the value used to resolve related attribute/media rows:

let feature = fc.features[0]
feature.id            // Feature.Identifier?   — the GeoJSON id (from the `id` column)
feature.gpkgRowId     // Int?                  — the SQLite rowid (from the `fid` PK)

On read-back, the id/fid column value is promoted to Feature.id based on the runtime SQLite cell type (Int.int, Double.double, String.string, NULLnil), so typed columns round-trip with type fidelity. The fid/id columns are never leaked into feature.properties.

Schema inspection

let tables = try await gpkg.readContents()
let featureTables = try await gpkg.featureTables()
let tileTables = try await gpkg.tileTables()

Property type mapping

Feature properties (attribute columns other than fid, id, and the geometry column) are mapped as follows:

| GeoPackage type | Swift type | |---|---| | INTEGER | Int | | REAL | Double | | TEXT | String | | BLOB | String (Base64) | | BOOLEAN | Bool |

Contributing

Please create an issue or open a pull request with a fix or enhancement.

License

MIT

Authors

Thomas Rasch, Outdooractive

Built on top of gis-tools.

Package Metadata

Repository: outdooractive/gis-tools-geopackage

Default branch: main

README: README.md