diff --git a/Sources/Turf/Feature.swift b/Sources/Turf/Feature.swift index 0af1dd80..4a09a784 100644 --- a/Sources/Turf/Feature.swift +++ b/Sources/Turf/Feature.swift @@ -25,9 +25,18 @@ public struct Feature: Equatable { - parameter geometry: The geometry at which the feature is located. */ - public init(geometry: Geometry?) { + public init(geometry: Geometry) { self.geometry = geometry } + + /** + Initializes a feature defined by the given geometry-convertible instance. + + - parameter geometry: The geometry-convertible instance that bounds the feature. + */ + public init(geometry: GeometryConvertible?) { + self.geometry = geometry?.geometry + } } extension Feature: Codable { diff --git a/Sources/Turf/FeatureIdentifier.swift b/Sources/Turf/FeatureIdentifier.swift index 041bda52..abe6781d 100644 --- a/Sources/Turf/FeatureIdentifier.swift +++ b/Sources/Turf/FeatureIdentifier.swift @@ -3,7 +3,7 @@ import Foundation /** A [feature identifier](https://datatracker.ietf.org/doc/html/rfc7946#section-3.2) identifies a `Feature` object. */ -public enum FeatureIdentifier: Equatable { +public enum FeatureIdentifier: Hashable { /// A string. case string(_ string: String) diff --git a/Sources/Turf/GeoJSON.swift b/Sources/Turf/GeoJSON.swift index 1aecb7c5..f9e99b0e 100644 --- a/Sources/Turf/GeoJSON.swift +++ b/Sources/Turf/GeoJSON.swift @@ -28,6 +28,11 @@ public enum GeoJSONObject: Equatable { - parameter featureCollection: The GeoJSON object as a FeatureCollection object. */ case featureCollection(_ featureCollection: FeatureCollection) + + /// Initializes a GeoJSON object representing the given GeoJSON object–convertible instance. + public init(_ object: GeoJSONObjectConvertible) { + self = object.geoJSONObject + } } extension GeoJSONObject: Codable { @@ -60,3 +65,27 @@ extension GeoJSONObject: Codable { } } } + +/** + A type that can be represented as a `GeoJSONObject` instance. + */ +public protocol GeoJSONObjectConvertible { + /// The instance wrapped in a `GeoJSONObject` instance. + var geoJSONObject: GeoJSONObject { get } +} + +extension GeoJSONObject: GeoJSONObjectConvertible { + public var geoJSONObject: GeoJSONObject { return self } +} + +extension Geometry: GeoJSONObjectConvertible { + public var geoJSONObject: GeoJSONObject { return .geometry(self) } +} + +extension Feature: GeoJSONObjectConvertible { + public var geoJSONObject: GeoJSONObject { return .feature(self) } +} + +extension FeatureCollection: GeoJSONObjectConvertible { + public var geoJSONObject: GeoJSONObject { return .featureCollection(self) } +} diff --git a/Sources/Turf/Geometry.swift b/Sources/Turf/Geometry.swift index caaf5520..f5e3c2aa 100644 --- a/Sources/Turf/Geometry.swift +++ b/Sources/Turf/Geometry.swift @@ -27,6 +27,11 @@ public enum Geometry: Equatable { /// A heterogeneous collection of geometries that are related. case geometryCollection(_ geometry: GeometryCollection) + + /// Initializes a geometry representing the given geometry–convertible instance. + public init(_ geometry: GeometryConvertible) { + self = geometry.geometry + } } extension Geometry: Codable { @@ -85,3 +90,43 @@ extension Geometry: Codable { } } } + +/** + A type that can be represented as a `Geometry` instance. + */ +public protocol GeometryConvertible { + /// The instance wrapped in a `Geometry` instance. + var geometry: Geometry { get } +} + +extension Geometry: GeometryConvertible { + public var geometry: Geometry { return self } +} + +extension Point: GeometryConvertible { + public var geometry: Geometry { return .point(self) } +} + +extension LineString: GeometryConvertible { + public var geometry: Geometry { return .lineString(self) } +} + +extension Polygon: GeometryConvertible { + public var geometry: Geometry { return .polygon(self) } +} + +extension MultiPoint: GeometryConvertible { + public var geometry: Geometry { return .multiPoint(self) } +} + +extension MultiLineString: GeometryConvertible { + public var geometry: Geometry { return .multiLineString(self) } +} + +extension MultiPolygon: GeometryConvertible { + public var geometry: Geometry { return .multiPolygon(self) } +} + +extension GeometryCollection: GeometryConvertible { + public var geometry: Geometry { return .geometryCollection(self) } +} diff --git a/Sources/Turf/JSON.swift b/Sources/Turf/JSON.swift index bf6cbe8c..2f298d4c 100644 --- a/Sources/Turf/JSON.swift +++ b/Sources/Turf/JSON.swift @@ -5,7 +5,7 @@ import Foundation This type does not represent the `null` value in JSON. Use `Optional` wherever `null` is accepted. */ -public enum JSONValue: Equatable { +public enum JSONValue: Hashable { // case null would be redundant to Optional.none /// A string. diff --git a/Tests/TurfTests/GeoJSONTests.swift b/Tests/TurfTests/GeoJSONTests.swift index af0269c6..e16e6aa4 100644 --- a/Tests/TurfTests/GeoJSONTests.swift +++ b/Tests/TurfTests/GeoJSONTests.swift @@ -6,6 +6,34 @@ import struct Turf.Polygon import CoreLocation class GeoJSONTests: XCTestCase { + func testConversion() { + let nullIsland = LocationCoordinate2D(latitude: 0, longitude: 0) + XCTAssertEqual(Geometry(Point(nullIsland)), + .point(Point(nullIsland))) + XCTAssertEqual(Geometry(LineString([nullIsland, nullIsland])), + .lineString(LineString([nullIsland, nullIsland]))) + XCTAssertEqual(Geometry(Polygon([[nullIsland, nullIsland, nullIsland]])), + .polygon(Polygon([[nullIsland, nullIsland, nullIsland]]))) + XCTAssertEqual(Geometry(MultiPoint([nullIsland, nullIsland, nullIsland])), + .multiPoint(MultiPoint([nullIsland, nullIsland, nullIsland]))) + XCTAssertEqual(Geometry(MultiLineString([[nullIsland, nullIsland, nullIsland]])), + .multiLineString(MultiLineString([[nullIsland, nullIsland, nullIsland]]))) + XCTAssertEqual(Geometry(MultiPolygon([[[nullIsland, nullIsland, nullIsland]]])), + .multiPolygon(MultiPolygon([[[nullIsland, nullIsland, nullIsland]]]))) + XCTAssertEqual(Geometry(GeometryCollection(geometries: [])), + .geometryCollection(GeometryCollection(geometries: []))) + + XCTAssertEqual(Geometry(Geometry(Geometry(Geometry(Point(nullIsland))))), .point(.init(nullIsland))) + + XCTAssertEqual(GeoJSONObject(Geometry(Point(nullIsland))), .geometry(.point(.init(nullIsland)))) + XCTAssertEqual(GeoJSONObject(Feature(geometry: nil)), .feature(.init(geometry: nil))) + let nullGeometry: Geometry? = nil + XCTAssertEqual(GeoJSONObject(Feature(geometry: nullGeometry)), .feature(.init(geometry: nil))) + XCTAssertEqual(GeoJSONObject(FeatureCollection(features: [])), .featureCollection(.init(features: []))) + + XCTAssertEqual(GeoJSONObject(GeoJSONObject(GeoJSONObject(GeoJSONObject(Geometry(Point(nullIsland)))))), + .geometry(.point(.init(nullIsland)))) + } func testPoint() { let coordinate = LocationCoordinate2D(latitude: 10, longitude: 30)