Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#44: SwiftData compatibility #47

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ targets: [

- Supports the full [GeoJSON standard][6], with some exceptions (see [TODO.md][7])
- Load and write GeoJSON objects from and to `[String:Any]`, `URL`, `Data` and `String`
- Supports `Codable`
- Supports `Codable` and `SwiftData` (see below)
- Supports EPSG:3857 (web mercator) and EPSG:4326 (geodetic) conversions
- Supports WKT/WKB, also with different projections
- Spatial search with a R-tree
Expand Down Expand Up @@ -655,6 +655,24 @@ func projected(to newProjection: Projection) -> FeatureCollection

This type is somewhat special since its initializers will accept any valid GeoJSON object and return a `FeatureCollection` with the input wrapped in `Feature` objects if the input are geometries, or by collecting the input if it’s a `Feature`.

# SwiftData

You need to use a transformer for using GeoJson with SwiftData (also have a look at the [SwiftData test cases](https://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/SwiftDataTests.swift)).

First, register the transformer like this:
```swift
GeoJsonTransformer.register()
```

Then create your models like this:
```swift
@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var geoJson: GeoJson?
@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var point: Point?
...
```

This is necessary because SwiftData doesn't work well with the default Codable implementation, so you need to do the serialization for yourself...

# WKB/WKT
The following geometry types are supported: `point`, `linestring`, `linearring`, `polygon`, `multipoint`, `multilinestring`, `multipolygon`, `geometrycollection` and `triangle`. Please open an issue if you need more.

Expand Down
38 changes: 38 additions & 0 deletions Sources/GISTools/GeoJson/GeoJsonCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,44 @@ extension Coordinate3D: Codable {

}

// MARK: - SwiftData compatibility (see the README)

#if canImport(SwiftData)
@objc(GeoJsonTransformer)
public final class GeoJsonTransformer: ValueTransformer {

public static let name = NSValueTransformerName(rawValue: "GeoJsonTransformer")

public static func register() {
ValueTransformer.setValueTransformer(GeoJsonTransformer(), forName: name)
}

public override class func transformedValueClass() -> AnyClass {
// returns __SwiftValue
type(of: Point(Coordinate3D.zero) as AnyObject)
}

public override class func allowsReverseTransformation() -> Bool {
true
}

// Encode GeoJSON to Data
public override func transformedValue(_ value: Any?) -> Any? {
guard let geoJson = value as? GeoJson else { return nil }

return geoJson.asJsonData()
}

// Decode Data to GeoJSON
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }

return GeoJsonReader.geoJsonFrom(jsonData: data)
}

}
#endif

// MARK: - Private

private struct GeoJsonCodingKey: CodingKey {
Expand Down
16 changes: 8 additions & 8 deletions Tests/GISToolsTests/GeoJson/FeatureCollectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class FeatureCollectionTests: XCTestCase {

private let featureCollectionJson = """
static let featureCollectionJson = """
{
"type": "FeatureCollection",
"features": [{
Expand Down Expand Up @@ -56,7 +56,7 @@ final class FeatureCollectionTests: XCTestCase {
"""

func testLoadJson() throws {
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

XCTAssertEqual(featureCollection.type, GeoJsonType.featureCollection)
XCTAssertEqual(featureCollection.projection, .epsg4326)
Expand Down Expand Up @@ -94,7 +94,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testMap() throws {
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

let prop0: String? = featureCollection.features.first?.property(for: "prop0")
XCTAssertEqual(prop0, "value0")
Expand All @@ -110,7 +110,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testCompactMap() throws {
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

XCTAssertEqual(featureCollection.features.count, 3)

Expand All @@ -123,7 +123,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testFilter() throws {
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
var featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

XCTAssertEqual(featureCollection.features.count, 3)

Expand All @@ -135,7 +135,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testEnumerate() throws {
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

let expected: [(Int, Int, Coordinate3D)] = [
(0, 0, Coordinate3D(latitude: 0.5, longitude: 102.0)),
Expand Down Expand Up @@ -165,7 +165,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testEncodable() throws {
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson))
let featureCollection = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -174,7 +174,7 @@ final class FeatureCollectionTests: XCTestCase {
}

func testDecodable() throws {
let featureCollectionData = try XCTUnwrap(FeatureCollection(jsonString: featureCollectionJson)?.asJsonData(prettyPrinted: true))
let featureCollectionData = try XCTUnwrap(FeatureCollection(jsonString: FeatureCollectionTests.featureCollectionJson)?.asJsonData(prettyPrinted: true))
let featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: featureCollectionData)

XCTAssertEqual(featureCollection.projection, .epsg4326)
Expand Down
12 changes: 6 additions & 6 deletions Tests/GISToolsTests/GeoJson/FeatureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class FeatureTests: XCTestCase {

private let featureJson = """
static let featureJson = """
{
"type": "Feature",
"geometry": {
Expand All @@ -30,7 +30,7 @@ final class FeatureTests: XCTestCase {
"""

func testLoadJson() throws {
let feature = try XCTUnwrap(Feature(jsonString: featureJson))
let feature = try XCTUnwrap(Feature(jsonString: FeatureTests.featureJson))

XCTAssertEqual(feature.type, GeoJsonType.feature)
XCTAssertEqual(feature.projection, .epsg4326)
Expand All @@ -44,7 +44,7 @@ final class FeatureTests: XCTestCase {
XCTAssertEqual(feature.id, .string("abcd.1234"))
}

private let featureJsonWithIntId = """
static let featureJsonWithIntId = """
{
"type": "Feature",
"geometry": {
Expand All @@ -71,7 +71,7 @@ final class FeatureTests: XCTestCase {
"""

func testLoadJsonWithIntId() throws {
let feature = try XCTUnwrap(Feature(jsonString: featureJsonWithIntId))
let feature = try XCTUnwrap(Feature(jsonString: FeatureTests.featureJsonWithIntId))

XCTAssertEqual(feature.id, .int(1234))
XCTAssertEqual(feature.projection, .epsg4326)
Expand All @@ -89,7 +89,7 @@ final class FeatureTests: XCTestCase {
}

func testEncodable() throws {
let feature = try XCTUnwrap(Feature(jsonString: featureJson))
let feature = try XCTUnwrap(Feature(jsonString: FeatureTests.featureJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -98,7 +98,7 @@ final class FeatureTests: XCTestCase {
}

func testDecodable() throws {
let featureData = try XCTUnwrap(Feature(jsonString: featureJson)?.asJsonData(prettyPrinted: true))
let featureData = try XCTUnwrap(Feature(jsonString: FeatureTests.featureJson)?.asJsonData(prettyPrinted: true))
let feature = try JSONDecoder().decode(Feature.self, from: featureData)

XCTAssertEqual(feature.projection, .epsg4326)
Expand Down
8 changes: 4 additions & 4 deletions Tests/GISToolsTests/GeoJson/GeometryCollectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class GeometryCollectionTests: XCTestCase {

private let geometryCollectionJson = """
static let geometryCollectionJson = """
{
"type": "GeometryCollection",
"geometries": [{
Expand All @@ -21,7 +21,7 @@ final class GeometryCollectionTests: XCTestCase {
"""

func testLoadJson() throws {
let geometryCollection = try XCTUnwrap(GeometryCollection(jsonString: geometryCollectionJson))
let geometryCollection = try XCTUnwrap(GeometryCollection(jsonString: GeometryCollectionTests.geometryCollectionJson))

XCTAssertNotNil(geometryCollection)
XCTAssertEqual(geometryCollection.type, GeoJsonType.geometryCollection)
Expand All @@ -44,7 +44,7 @@ final class GeometryCollectionTests: XCTestCase {
}

func testEncodable() throws {
let geometryCollection = try XCTUnwrap(GeometryCollection(jsonString: geometryCollectionJson))
let geometryCollection = try XCTUnwrap(GeometryCollection(jsonString: GeometryCollectionTests.geometryCollectionJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -53,7 +53,7 @@ final class GeometryCollectionTests: XCTestCase {
}

func testDecodable() throws {
let geometryCollectionData = try XCTUnwrap(GeometryCollection(jsonString: geometryCollectionJson)?.asJsonData(prettyPrinted: true))
let geometryCollectionData = try XCTUnwrap(GeometryCollection(jsonString: GeometryCollectionTests.geometryCollectionJson)?.asJsonData(prettyPrinted: true))
let geometryCollection = try JSONDecoder().decode(GeometryCollection.self, from: geometryCollectionData)

XCTAssertEqual(geometryCollection.projection, .epsg4326)
Expand Down
8 changes: 4 additions & 4 deletions Tests/GISToolsTests/GeoJson/LineStringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class LineStringTests: XCTestCase {

private let lineStringJson = """
static let lineStringJson = """
{
"type": "LineString",
"coordinates": [
Expand All @@ -15,7 +15,7 @@ final class LineStringTests: XCTestCase {
"""

func testLoadJson() throws {
let lineString = try XCTUnwrap(LineString(jsonString: lineStringJson))
let lineString = try XCTUnwrap(LineString(jsonString: LineStringTests.lineStringJson))

XCTAssertEqual(lineString.type, GeoJsonType.lineString)
XCTAssertEqual(lineString.projection, .epsg4326)
Expand Down Expand Up @@ -52,7 +52,7 @@ final class LineStringTests: XCTestCase {
}

func testEncodable() throws {
let lineString = try XCTUnwrap(LineString(jsonString: lineStringJson))
let lineString = try XCTUnwrap(LineString(jsonString: LineStringTests.lineStringJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -61,7 +61,7 @@ final class LineStringTests: XCTestCase {
}

func testDecodable() throws {
let lineStringData = try XCTUnwrap(LineString(jsonString: lineStringJson)?.asJsonData(prettyPrinted: true))
let lineStringData = try XCTUnwrap(LineString(jsonString: LineStringTests.lineStringJson)?.asJsonData(prettyPrinted: true))
let lineString = try JSONDecoder().decode(LineString.self, from: lineStringData)

XCTAssertEqual(lineString.projection, .epsg4326)
Expand Down
8 changes: 4 additions & 4 deletions Tests/GISToolsTests/GeoJson/MultiLineStringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class MultiLineStringTests: XCTestCase {

private let multiLineStringJson = """
static let multiLineStringJson = """
{
"type": "MultiLineString",
"coordinates": [
Expand All @@ -21,7 +21,7 @@ final class MultiLineStringTests: XCTestCase {
"""

func testLoadJson() throws {
let multiLineString = try XCTUnwrap(MultiLineString(jsonString: multiLineStringJson))
let multiLineString = try XCTUnwrap(MultiLineString(jsonString: MultiLineStringTests.multiLineStringJson))

XCTAssertNotNil(multiLineString)
XCTAssertEqual(multiLineString.type, GeoJsonType.multiLineString)
Expand Down Expand Up @@ -50,7 +50,7 @@ final class MultiLineStringTests: XCTestCase {
}

func testEncodable() throws {
let multiLineString = try XCTUnwrap(MultiLineString(jsonString: multiLineStringJson))
let multiLineString = try XCTUnwrap(MultiLineString(jsonString: MultiLineStringTests.multiLineStringJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -59,7 +59,7 @@ final class MultiLineStringTests: XCTestCase {
}

func testDecodable() throws {
let multiLineStringData = try XCTUnwrap(MultiLineString(jsonString: multiLineStringJson)?.asJsonData(prettyPrinted: true))
let multiLineStringData = try XCTUnwrap(MultiLineString(jsonString: MultiLineStringTests.multiLineStringJson)?.asJsonData(prettyPrinted: true))
let multiLineString = try JSONDecoder().decode(MultiLineString.self, from: multiLineStringData)

XCTAssertEqual(multiLineString.projection, .epsg4326)
Expand Down
8 changes: 4 additions & 4 deletions Tests/GISToolsTests/GeoJson/MultiPointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class MultiPointTests: XCTestCase {

private let multiPointJson = """
static let multiPointJson = """
{
"type": "MultiPoint",
"coordinates": [
Expand All @@ -15,7 +15,7 @@ final class MultiPointTests: XCTestCase {
"""

func testLoadJson() throws {
let multiPoint = try XCTUnwrap(MultiPoint(jsonString: multiPointJson))
let multiPoint = try XCTUnwrap(MultiPoint(jsonString: MultiPointTests.multiPointJson))

XCTAssertNotNil(multiPoint)
XCTAssertEqual(multiPoint.type, GeoJsonType.multiPoint)
Expand All @@ -35,7 +35,7 @@ final class MultiPointTests: XCTestCase {
}

func testEncodable() throws {
let multiPoint = try XCTUnwrap(MultiPoint(jsonString: multiPointJson))
let multiPoint = try XCTUnwrap(MultiPoint(jsonString: MultiPointTests.multiPointJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -44,7 +44,7 @@ final class MultiPointTests: XCTestCase {
}

func testDecodable() throws {
let multiPointData = try XCTUnwrap(MultiPoint(jsonString: multiPointJson)?.asJsonData(prettyPrinted: true))
let multiPointData = try XCTUnwrap(MultiPoint(jsonString: MultiPointTests.multiPointJson)?.asJsonData(prettyPrinted: true))
let multiPoint = try JSONDecoder().decode(MultiPoint.self, from: multiPointData)

XCTAssertEqual(multiPoint.projection, .epsg4326)
Expand Down
8 changes: 4 additions & 4 deletions Tests/GISToolsTests/GeoJson/MultiPolygonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

final class MultiPolygonTests: XCTestCase {

private let multiPolygonJson = """
static let multiPolygonJson = """
{
"type": "MultiPolygon",
"coordinates": [
Expand Down Expand Up @@ -38,7 +38,7 @@ final class MultiPolygonTests: XCTestCase {
"""

func testLoadJson() throws {
let multiPolygon = try XCTUnwrap(MultiPolygon(jsonString: multiPolygonJson))
let multiPolygon = try XCTUnwrap(MultiPolygon(jsonString: MultiPolygonTests.multiPolygonJson))

XCTAssertEqual(multiPolygon.type, GeoJsonType.multiPolygon)
XCTAssertEqual(multiPolygon.projection, .epsg4326)
Expand All @@ -57,7 +57,7 @@ final class MultiPolygonTests: XCTestCase {
}

func testEncodable() throws {
let multiPolygon = try XCTUnwrap(MultiPolygon(jsonString: multiPolygonJson))
let multiPolygon = try XCTUnwrap(MultiPolygon(jsonString: MultiPolygonTests.multiPolygonJson))

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Expand All @@ -66,7 +66,7 @@ final class MultiPolygonTests: XCTestCase {
}

func testDecodable() throws {
let multiPolygonData = try XCTUnwrap(MultiPolygon(jsonString: multiPolygonJson)?.asJsonData(prettyPrinted: true))
let multiPolygonData = try XCTUnwrap(MultiPolygon(jsonString: MultiPolygonTests.multiPolygonJson)?.asJsonData(prettyPrinted: true))
let multiPolygon = try JSONDecoder().decode(MultiPolygon.self, from: multiPolygonData)

XCTAssertEqual(multiPolygon.projection, .epsg4326)
Expand Down
Loading
Loading