Add GeoPackage Related Tables Extension support#211
Merged
Conversation
- Add gpkgext_relations, gpkg_data_columns, gpkg_data_column_constraints to createMetadataSQL / dropMetadataSQL - Add RelationshipType enum, RelationRow struct, and GeoPackage helpers for reading/writing relationships and registering the extension - Add AttributeTable struct with GeoPackage.readAttributeTable/ writeAttributeTable (supports optional rowId filter for fast lookup) - Add MediaTable struct with GeoPackage.readMediaTable/writeMediaTable - Store GeoPackage row ID in Feature.id when reading features - Add Feature.relatedAttributes(from:using:) for per-feature lookup - Add FeatureCollection.loadRelatedAttributes/loadRelatedMedia by table name resolving gpkgext_relations entries - 6 tests: attribute/media round-trip, relationships, validation, FeatureCollection.loadRelatedAttributes, Feature.relatedAttributes
- SQLiteDB: @unchecked Sendable with internal serial DispatchQueue for thread-safe C API access - GeoPackageConnection: actor (all public methods are async) - FeatureCollection(geopackage:): now async throws - FeatureStreamAsyncIterator: uses SQLiteDB directly - Add write(sql:values:) convenience method on SQLiteDB - All existing convenience APIs (GeoPackage.readAttributeTable, GeoPackage.validate, etc.) remain synchronous — they create their own SQLiteDB internally - Internal helpers (GeoPackageReader, GeoPackageWriter, TileReader, TileWriter) stay synchronous - Fix all test call sites with await where needed - Restore GeoPackageError named associated values (path:, detail:, type:) - Restore DocC comments on GeoPackageValidationIssue cases - Remove return keyword from single-expression switch arms - Re-add GeoPackage.validate(in: SQLiteDB) for connection-based usage
- Add gpkgTableName property on Feature via foreignMembers - Add MediaRow struct for single media entry representation - Add rowId filter to readMediaTable (SQL-level WHERE id = ?) - Set gpkgTableName automatically in GeoPackageReader and FeatureStream - Replace Feature.relatedMedia/relatedAttributes with connection-based auto-resolve versions using gpkgTableName and id - Add GeoPackageConnection.readMediaRows method - Remove old URL-based RelationRow-requiring Feature methods - Add DocC documentation to all public types, functions, parameters, and enum cases across the GeoPackage module
- Add GeoPackageConnection.write(features: [Feature], to:) for incremental/streaming writes (first call creates table, subsequent calls append) - Refactor GeoPackageWriter into reusable building blocks (createFeatureTable, writeFeatureRows, buildSpatialIndex, updateContentsBounds, writeContentsMetadata) - writeGeopackage now async throws via GeoPackageConnection - GeoPackageConnection.init validates metadata by default (skipValidation: Bool to opt out) - Add featureTables() and tileTables() convenience filters - SQLiteDB: @unchecked Sendable with internal serial queue - Make GeoPackageConnection an actor - Fix LEAST/GREATEST SQLite compatibility in bounds update - Use INSERT OR REPLACE / INSERT OR IGNORE for idempotent writes - Clean up test files: strip () from #function, add dbUrl pattern with FileManager.default.removeItem(at:) before each test - All 1583 tests pass, zero warnings
Add FeatureCollection.writeGeopackage(into:table:createSpatialIndex:) so users can write features and read them back on the same GeoPackageConnection without reopening the file.
- Add gpkgRowId convenience property on Feature backed by foreignMembers["_gpkg_rowid"] - Stop setting feature.id = .int(rowId) — Feature.id is now free for GeoJSON "id" values - Update Feature.relatedAttributes/relatedMedia to use gpkgRowId instead of case .int(let rowId) = id - Update test to check feature.gpkgRowId instead of feature.id
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds support for the GeoPackage Related Tables Extension (OGC 18-000), enabling non-spatial attribute tables, media blobs, and foreign key relationships between feature tables and associated data tables.
New files (3)
RelationshipTable.swiftRelationshipTypeenum,RelationRowstruct,GeoPackage.readRelations/writeRelation/registerRelatedTablesExtension,Feature.relatedAttributes(from:using:),FeatureCollection.loadRelatedAttributes/loadRelatedMedia/relationshipsAttributeTable.swiftAttributeTablestruct,GeoPackage.readAttributeTable/writeAttributeTablewith optionalrowIdfilter for SQL-level filteringMediaTable.swiftMediaTablestruct,GeoPackage.readMediaTable/writeMediaTableModified files (2)
GeoPackage.swiftgpkgext_relations,gpkg_data_columns,gpkg_data_column_constraintstocreateMetadataSQL/dropMetadataSQLGeoPackageReader.swiftfeature.id = .int(rowId)when reading features (enables per-feature related data lookup)Key design
readAttributeTable(from:table:rowId:) usesWHERE id = ?at the SQL level when filtering by a specific row — no in-memory filteringFeature.idstores the GeoPackage row ID directly (noforeignMembershack)FeatureCollection.loadRelatedAttributes/loadRelatedMediaresolvegpkgext_relationsentries and load the full related tableFeature.relatedAttributes(from:using:)reads only the matching row via SQL filterTests (6)
attributeTableRoundTrip, mediaTableRoundTrip, relationshipsRoundTrip, attributeTableValidation, loadRelatedAttributesViaFeatureCollection, featureRelatedAttributes
Closes #189