diff --git a/Sources/GeoJSON/GeoJSON+Codable.swift b/Sources/GeoJSON/GeoJSON+Codable.swift index d35cb5a..f86627b 100644 --- a/Sources/GeoJSON/GeoJSON+Codable.swift +++ b/Sources/GeoJSON/GeoJSON+Codable.swift @@ -391,7 +391,7 @@ extension Feature { )) } - let geometry = try container.decodeIfPresent(AnyGeometry.self, forKey: .geometry) + let geometry = try container.decodeIfPresent(Geometry.self, forKey: .geometry) let properties = try container.decode(Properties.self, forKey: .properties) self.init(geometry: geometry, properties: properties) @@ -428,7 +428,10 @@ extension FeatureCollection { )) } - let features = try container.decodeIfPresent([Feature].self, forKey: .features) ?? [] + let features = try container.decodeIfPresent( + [Feature].self, + forKey: .features + ) ?? [] self.init(features: features) } diff --git a/Sources/GeoJSON/GeoJSON.docc/Documentation.md b/Sources/GeoJSON/GeoJSON.docc/Documentation.md index 6030082..769ce58 100644 --- a/Sources/GeoJSON/GeoJSON.docc/Documentation.md +++ b/Sources/GeoJSON/GeoJSON.docc/Documentation.md @@ -15,9 +15,12 @@ ### Objects - ``Object`` +- ``CodableObject`` - - ``Feature`` +- ``AnyFeature`` - ``FeatureCollection`` +- ``AnyFeatureCollection`` ### Bounding Boxes diff --git a/Sources/GeoJSON/GeoJSON.docc/Geometries.md b/Sources/GeoJSON/GeoJSON.docc/Geometries.md index 0b96368..ec387f0 100644 --- a/Sources/GeoJSON/GeoJSON.docc/Geometries.md +++ b/Sources/GeoJSON/GeoJSON.docc/Geometries.md @@ -5,6 +5,7 @@ ### Base - ``Geometry`` +- ``CodableGeometry`` - ``AnyGeometry`` ### Introduced concepts diff --git a/Sources/GeoJSON/Geometries/GeometryCollection.swift b/Sources/GeoJSON/Geometries/GeometryCollection.swift index 60b90d0..5b3070a 100644 --- a/Sources/GeoJSON/Geometries/GeometryCollection.swift +++ b/Sources/GeoJSON/Geometries/GeometryCollection.swift @@ -7,7 +7,7 @@ // /// A [GeoJSON GeometryCollection](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8). -public struct GeometryCollection: Geometry { +public struct GeometryCollection: CodableGeometry { public static var geometryType: GeoJSON.`Type`.Geometry { .geometryCollection } diff --git a/Sources/GeoJSON/Geometries/LineString.swift b/Sources/GeoJSON/Geometries/LineString.swift index dd42337..1659814 100644 --- a/Sources/GeoJSON/Geometries/LineString.swift +++ b/Sources/GeoJSON/Geometries/LineString.swift @@ -16,13 +16,17 @@ public protocol LineString: SingleGeometry { } +extension LineString { + + public static var geometryType: GeoJSON.`Type`.Geometry { .lineString } + +} + /// A ``LineString`` with ``Point2D``s. public struct LineString2D: LineString { public typealias Position = Position2D - public static var geometryType: GeoJSON.`Type`.Geometry { .lineString } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .lineString2D(self) } @@ -46,8 +50,6 @@ public struct LineString3D: LineString { public typealias Position = Position3D - public static var geometryType: GeoJSON.`Type`.Geometry { .lineString } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .lineString3D(self) } diff --git a/Sources/GeoJSON/Geometries/MultiLineString.swift b/Sources/GeoJSON/Geometries/MultiLineString.swift index 08469cf..1eb6f8c 100644 --- a/Sources/GeoJSON/Geometries/MultiLineString.swift +++ b/Sources/GeoJSON/Geometries/MultiLineString.swift @@ -16,13 +16,17 @@ public protocol MultiLineString: SingleGeometry { } +extension MultiLineString { + + public static var geometryType: GeoJSON.`Type`.Geometry { .multiLineString } + +} + /// A ``MultiLineString`` with ``Point2D``s. public struct MultiLineString2D: MultiLineString { public typealias LineString = LineString2D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiLineString } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiLineString2D(self) } @@ -52,8 +56,6 @@ public struct MultiLineString3D: MultiLineString { public typealias LineString = LineString3D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiLineString } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiLineString3D(self) } diff --git a/Sources/GeoJSON/Geometries/MultiPoint.swift b/Sources/GeoJSON/Geometries/MultiPoint.swift index 5a1746d..381c87c 100644 --- a/Sources/GeoJSON/Geometries/MultiPoint.swift +++ b/Sources/GeoJSON/Geometries/MultiPoint.swift @@ -14,13 +14,17 @@ public protocol MultiPoint: SingleGeometry { } +extension MultiPoint { + + public static var geometryType: GeoJSON.`Type`.Geometry { .multiPoint } + +} + /// A ``MultiPoint`` with ``Point2D``s. public struct MultiPoint2D: MultiPoint { public typealias Point = Point2D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiPoint } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiPoint2D(self) } @@ -36,8 +40,6 @@ public struct MultiPoint3D: MultiPoint { public typealias Point = Point3D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiPoint } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiPoint3D(self) } diff --git a/Sources/GeoJSON/Geometries/MultiPolygon.swift b/Sources/GeoJSON/Geometries/MultiPolygon.swift index ac01a21..bb477d2 100644 --- a/Sources/GeoJSON/Geometries/MultiPolygon.swift +++ b/Sources/GeoJSON/Geometries/MultiPolygon.swift @@ -14,13 +14,17 @@ public protocol MultiPolygon: SingleGeometry { } +extension MultiPolygon { + + public static var geometryType: GeoJSON.`Type`.Geometry { .multiPolygon } + +} + /// A ``MultiPolygon`` with ``Point2D``s. public struct MultiPolygon2D: MultiPolygon { public typealias Polygon = Polygon2D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiPolygon } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiPolygon2D(self) } @@ -36,8 +40,6 @@ public struct MultiPolygon3D: MultiPolygon { public typealias Polygon = Polygon3D - public static var geometryType: GeoJSON.`Type`.Geometry { .multiPolygon } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .multiPolygon3D(self) } diff --git a/Sources/GeoJSON/Geometries/Point.swift b/Sources/GeoJSON/Geometries/Point.swift index 228f361..968d013 100644 --- a/Sources/GeoJSON/Geometries/Point.swift +++ b/Sources/GeoJSON/Geometries/Point.swift @@ -14,13 +14,17 @@ public protocol Point: SingleGeometry { } +extension Point { + + public static var geometryType: GeoJSON.`Type`.Geometry { .point } + +} + /// A two-dimensional ``Point`` (with longitude and latitude). public struct Point2D: Point { public typealias Position = Position2D - public static var geometryType: GeoJSON.`Type`.Geometry { .point } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .point2D(self) } @@ -36,8 +40,6 @@ public struct Point3D: Point { public typealias Position = Position3D - public static var geometryType: GeoJSON.`Type`.Geometry { .point } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .point3D(self) } diff --git a/Sources/GeoJSON/Geometries/Polygon.swift b/Sources/GeoJSON/Geometries/Polygon.swift index bee3fae..d74dd81 100644 --- a/Sources/GeoJSON/Geometries/Polygon.swift +++ b/Sources/GeoJSON/Geometries/Polygon.swift @@ -17,6 +17,12 @@ public protocol Polygon: SingleGeometry { } +extension Polygon { + + public static var geometryType: GeoJSON.`Type`.Geometry { .polygon } + +} + public struct LinearRingCoordinates: Boundable, Hashable, Codable { public typealias RawValue = NonEmpty>>> @@ -61,8 +67,6 @@ public struct Polygon2D: Polygon { public typealias Point = Point2D - public static var geometryType: GeoJSON.`Type`.Geometry { .polygon } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .polygon2D(self) } @@ -78,8 +82,6 @@ public struct Polygon3D: Polygon { public typealias Point = Point3D - public static var geometryType: GeoJSON.`Type`.Geometry { .polygon } - public var coordinates: Coordinates public var asAnyGeometry: AnyGeometry { .polygon3D(self) } diff --git a/Sources/GeoJSON/Objects/Feature.swift b/Sources/GeoJSON/Objects/Feature.swift index df1b5fa..47464b3 100644 --- a/Sources/GeoJSON/Objects/Feature.swift +++ b/Sources/GeoJSON/Objects/Feature.swift @@ -8,21 +8,23 @@ /// A [GeoJSON Feature](https://datatracker.ietf.org/doc/html/rfc7946#section-3.2). public struct Feature< -// Geometry: GeoJSON.Geometry, -// BoundingBox: GeoJSON.BoundingBox, + Geometry: GeoJSON.Geometry & Codable, Properties: GeoJSON.FeatureProperties ->: GeoJSON.Object { +>: CodableObject { public static var geoJSONType: GeoJSON.`Type` { .feature } - public var bbox: AnyBoundingBox? { geometry?.bbox } + public var bbox: Geometry.BoundingBox? { geometry?.bbox } - public var geometry: AnyGeometry? + public var geometry: Geometry? public var properties: Properties - public init(geometry: AnyGeometry?, properties: Properties) { + public init(geometry: Geometry?, properties: Properties) { self.geometry = geometry self.properties = properties } } + +/// A (half) type-erased ``Feature``. +public typealias AnyFeature = Feature diff --git a/Sources/GeoJSON/Objects/FeatureCollection.swift b/Sources/GeoJSON/Objects/FeatureCollection.swift index 28f4818..51fba32 100644 --- a/Sources/GeoJSON/Objects/FeatureCollection.swift +++ b/Sources/GeoJSON/Objects/FeatureCollection.swift @@ -8,15 +8,20 @@ /// A [GeoJSON FeatureCollection](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3). public struct FeatureCollection< -// BoundingBox: GeoJSON.BoundingBox, + Geometry: GeoJSON.Geometry & Codable, Properties: GeoJSON.FeatureProperties ->: GeoJSON.Object { +>: CodableObject { public static var geoJSONType: GeoJSON.`Type` { .featureCollection } - public var features: [Feature] + public var features: [Feature] // FIXME: Fix bounding box public var bbox: AnyBoundingBox? { nil } } + +/// A (half) type-erased ``FeatureCollection``. +public typealias AnyFeatureCollection< + Properties: GeoJSON.FeatureProperties +> = FeatureCollection diff --git a/Sources/GeoJSON/Objects/Geometry.swift b/Sources/GeoJSON/Objects/Geometry.swift index 6344c2b..29be8a9 100644 --- a/Sources/GeoJSON/Objects/Geometry.swift +++ b/Sources/GeoJSON/Objects/Geometry.swift @@ -11,9 +11,6 @@ import Turf /// A [GeoJSON Geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1). public protocol Geometry: GeoJSON.Object { - /// The GeoJSON type of this geometry. - static var geometryType: GeoJSON.`Type`.Geometry { get } - var bbox: BoundingBox? { get } /// This geometry, but type-erased. @@ -21,7 +18,14 @@ public protocol Geometry: GeoJSON.Object { } -extension Geometry { +public protocol CodableGeometry: Geometry, CodableObject { + + /// The GeoJSON type of this geometry. + static var geometryType: GeoJSON.`Type`.Geometry { get } + +} + +extension CodableGeometry { public static var geoJSONType: GeoJSON.`Type` { .geometry(Self.geometryType) } @@ -29,7 +33,7 @@ extension Geometry { /// A single [GeoJSON Geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1) /// (not a [GeometryCollection](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8)). -public protocol SingleGeometry: Geometry { +public protocol SingleGeometry: CodableGeometry { associatedtype Coordinates: Boundable & Hashable & Codable associatedtype BoundingBox = Coordinates.BoundingBox @@ -47,7 +51,7 @@ extension SingleGeometry { } /// A type-erased ``Geometry``. -public enum AnyGeometry: Hashable, Codable { +public enum AnyGeometry: Geometry, Hashable, Codable { case geometryCollection(GeometryCollection) @@ -65,37 +69,46 @@ public enum AnyGeometry: Hashable, Codable { case polygon3D(Polygon3D) case multiPolygon3D(MultiPolygon3D) +// public var geometryType: GeoJSON.`Type`.Geometry { +// switch self { +// case .geometryCollection(let geo): return geo.geometryType +// +// case .point2D(let geo): return geo.geometryType +// case .multiPoint2D(let geo): return geo.geometryType +// case .lineString2D(let geo): return geo.geometryType +// case .multiLineString2D(let geo): return geo.geometryType +// case .polygon2D(let geo): return geo.geometryType +// case .multiPolygon2D(let geo): return geo.geometryType +// +// case .point3D(let geo): return geo.geometryType +// case .multiPoint3D(let geo): return geo.geometryType +// case .lineString3D(let geo): return geo.geometryType +// case .multiLineString3D(let geo): return geo.geometryType +// case .polygon3D(let geo): return geo.geometryType +// case .multiPolygon3D(let geo): return geo.geometryType +// } +// } + public var bbox: AnyBoundingBox? { switch self { - case .geometryCollection(let geometryCollection): - return geometryCollection.bbox + case .geometryCollection(let geo): return geo.bbox - case .point2D(let point2D): - return point2D.bbox?.asAny - case .multiPoint2D(let multiPoint2D): - return multiPoint2D.bbox?.asAny - case .lineString2D(let lineString2D): - return lineString2D.bbox?.asAny - case .multiLineString2D(let multiLineString2D): - return multiLineString2D.bbox?.asAny - case .polygon2D(let polygon2D): - return polygon2D.bbox?.asAny - case .multiPolygon2D(let multiPolygon2D): - return multiPolygon2D.bbox?.asAny + case .point2D(let geo): return geo.bbox?.asAny + case .multiPoint2D(let geo): return geo.bbox?.asAny + case .lineString2D(let geo): return geo.bbox?.asAny + case .multiLineString2D(let geo): return geo.bbox?.asAny + case .polygon2D(let geo): return geo.bbox?.asAny + case .multiPolygon2D(let geo): return geo.bbox?.asAny - case .point3D(let point3D): - return point3D.bbox?.asAny - case .multiPoint3D(let multiPoint3D): - return multiPoint3D.bbox?.asAny - case .lineString3D(let lineString3D): - return lineString3D.bbox?.asAny - case .multiLineString3D(let multiLineString3D): - return multiLineString3D.bbox?.asAny - case .polygon3D(let polygon3D): - return polygon3D.bbox?.asAny - case .multiPolygon3D(let multiPolygon3D): - return multiPolygon3D.bbox?.asAny + case .point3D(let geo): return geo.bbox?.asAny + case .multiPoint3D(let geo): return geo.bbox?.asAny + case .lineString3D(let geo): return geo.bbox?.asAny + case .multiLineString3D(let geo): return geo.bbox?.asAny + case .polygon3D(let geo): return geo.bbox?.asAny + case .multiPolygon3D(let geo): return geo.bbox?.asAny } } + public var asAnyGeometry: AnyGeometry { self } + } diff --git a/Sources/GeoJSON/Objects/Object.swift b/Sources/GeoJSON/Objects/Object.swift index 61406e4..7ff4878 100644 --- a/Sources/GeoJSON/Objects/Object.swift +++ b/Sources/GeoJSON/Objects/Object.swift @@ -7,14 +7,18 @@ // /// A [GeoJSON Object](https://datatracker.ietf.org/doc/html/rfc7946#section-3). -public protocol Object: Hashable, Codable { +public protocol Object: Hashable { associatedtype BoundingBox: GeoJSON.BoundingBox + var bbox: BoundingBox? { get } + +} + +public protocol CodableObject: GeoJSON.Object, Codable { + /// The [GeoJSON type](https://datatracker.ietf.org/doc/html/rfc7946#section-1.4) of this /// [GeoJSON object](https://datatracker.ietf.org/doc/html/rfc7946#section-3). static var geoJSONType: GeoJSON.`Type` { get } - var bbox: BoundingBox? { get } - } diff --git a/Tests/GeoJSONTests/GeoJSON+DecodableTests.swift b/Tests/GeoJSONTests/GeoJSON+DecodableTests.swift index 0800c02..a13a368 100644 --- a/Tests/GeoJSONTests/GeoJSON+DecodableTests.swift +++ b/Tests/GeoJSONTests/GeoJSON+DecodableTests.swift @@ -170,10 +170,10 @@ final class GeoJSONDecodableTests: XCTestCase { ].joined() let data: Data = try XCTUnwrap(string.data(using: .utf8)) - let feature = try JSONDecoder().decode(Feature.self, from: data) + let feature = try JSONDecoder().decode(Feature.self, from: data) let expected: Feature = Feature( - geometry: .point2D(Point2D(coordinates: .nantes)), + geometry: Point2D(coordinates: .nantes), properties: FeatureProperties() ) XCTAssertEqual(feature, expected) @@ -200,9 +200,9 @@ final class GeoJSONDecodableTests: XCTestCase { """ let data: Data = try XCTUnwrap(string.data(using: .utf8)) - let feature = try JSONDecoder().decode(Feature.self, from: data) + let feature = try JSONDecoder().decode(AnyFeature.self, from: data) - let expected: Feature = Feature( + let expected: AnyFeature = AnyFeature( geometry: .point2D(Point2D(coordinates: .init(latitude: 0.5, longitude: 102))), properties: RealWorldProperties(prop0: "value0") ) @@ -337,9 +337,9 @@ final class GeoJSONDecodableTests: XCTestCase { """ let data: Data = try XCTUnwrap(string.data(using: .utf8)) - let feature = try JSONDecoder().decode(FeatureCollection.self, from: data) + let feature = try JSONDecoder().decode(AnyFeatureCollection.self, from: data) - let expected: FeatureCollection = FeatureCollection(features: [ + let expected: AnyFeatureCollection = AnyFeatureCollection(features: [ Feature( geometry: .point2D(Point2D(coordinates: .init(latitude: 0.5, longitude: 102))), properties: .type1(.init(prop0: "value0")) diff --git a/Tests/GeoJSONTests/GeoJSON+EncodableTests.swift b/Tests/GeoJSONTests/GeoJSON+EncodableTests.swift index 7369974..3f9b7d4 100644 --- a/Tests/GeoJSONTests/GeoJSON+EncodableTests.swift +++ b/Tests/GeoJSONTests/GeoJSON+EncodableTests.swift @@ -142,7 +142,7 @@ final class GeoJSONEncodableTests: XCTestCase { struct FeatureProperties: Hashable, Codable {} let feature: Feature = Feature( - geometry: .point2D(Point2D(coordinates: .nantes)), + geometry: Point2D(coordinates: .nantes), properties: FeatureProperties() ) let data: Data = try JSONEncoder().encode(feature)