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)
  • Relationships — gpkgext_relations for linking feature, attribute, and media tables
  • Schema validation — GeoPackage.validate(url:) checks spec compliance
  • Async streaming — FeatureStream yields features one at a time via AsyncSequence
  • Thread-safe connection — GeoPackageConnection 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:

  • fid — INTEGER 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, NULL → nil), 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