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

Round-trip GeoJSON foreign members #175

Merged
merged 3 commits into from
Dec 15, 2021
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
24 changes: 13 additions & 11 deletions Sources/Turf/Codable.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import Foundation
#if !os(Linux)
import CoreLocation
#endif

extension Ring: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self = Ring(coordinates: try container.decode([LocationCoordinate2DCodable].self).decodedCoordinates)
/**
A coding key as an extensible enumeration.
*/
struct AnyCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(coordinates.codableCoordinates)
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}

6 changes: 5 additions & 1 deletion Sources/Turf/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import CoreLocation
/**
A [Feature object](https://datatracker.ietf.org/doc/html/rfc7946#section-3.2) represents a spatially bounded thing.
*/
public struct Feature: Equatable {
public struct Feature: Equatable, ForeignMemberContainer {
/**
A string or number that commonly identifies the feature in the context of a data set.

Expand All @@ -20,6 +20,8 @@ public struct Feature: Equatable {
/// The geometry at which the feature is located.
public var geometry: Geometry?

public var foreignMembers: JSONObject = [:]

/**
Initializes a feature located at the given geometry.

Expand Down Expand Up @@ -57,6 +59,7 @@ extension Feature: Codable {
geometry = try container.decodeIfPresent(Geometry.self, forKey: .geometry)
properties = try container.decodeIfPresent(JSONObject.self, forKey: .properties)
identifier = try container.decodeIfPresent(FeatureIdentifier.self, forKey: .identifier)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -65,5 +68,6 @@ extension Feature: Codable {
try container.encode(geometry, forKey: .geometry)
try container.encodeIfPresent(properties, forKey: .properties)
try container.encodeIfPresent(identifier, forKey: .identifier)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/FeatureCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import Foundation
/**
A [FeatureCollection object](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3) is a collection of Feature objects.
*/
public struct FeatureCollection: Equatable {
public struct FeatureCollection: Equatable, ForeignMemberContainer {
/// The features that the collection contains.
public var features: [Feature] = []

public var foreignMembers: JSONObject = [:]

/**
Initializes a feature collection containing the given features.

Expand All @@ -31,11 +33,13 @@ extension FeatureCollection: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self)
_ = try container.decode(Kind.self, forKey: .kind)
features = try container.decode([Feature].self, forKey: .features)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.FeatureCollection, forKey: .kind)
try container.encode(features, forKey: .features)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
35 changes: 35 additions & 0 deletions Sources/Turf/GeoJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,38 @@ extension Feature: GeoJSONObjectConvertible {
extension FeatureCollection: GeoJSONObjectConvertible {
public var geoJSONObject: GeoJSONObject { return .featureCollection(self) }
}

/**
A GeoJSON object that can contain [foreign members](https://datatracker.ietf.org/doc/html/rfc7946#section-6.1) in arbitrary keys.
*/
public protocol ForeignMemberContainer {
/// [Foreign members](https://datatracker.ietf.org/doc/html/rfc7946#section-6.1) to round-trip to JSON.
var foreignMembers: JSONObject { get set }
}

extension ForeignMemberContainer {
/**
Decodes any foreign members using the given decoder.
*/
mutating func decodeForeignMembers<WellKnownCodingKeys>(notKeyedBy _: WellKnownCodingKeys.Type, with decoder: Decoder) throws where WellKnownCodingKeys: CodingKey {
let foreignMemberContainer = try decoder.container(keyedBy: AnyCodingKey.self)
for key in foreignMemberContainer.allKeys {
if WellKnownCodingKeys(stringValue: key.stringValue) == nil {
foreignMembers[key.stringValue] = try foreignMemberContainer.decode(JSONValue?.self, forKey: key)
}
}
}

/**
Encodes any foreign members using the given encoder.
*/
func encodeForeignMembers<WellKnownCodingKeys>(notKeyedBy _: WellKnownCodingKeys.Type, to encoder: Encoder) throws where WellKnownCodingKeys: CodingKey {
var foreignMemberContainer = encoder.container(keyedBy: AnyCodingKey.self)
for (key, value) in foreignMembers {
if let key = AnyCodingKey(stringValue: key),
WellKnownCodingKeys(stringValue: key.stringValue) == nil {
try foreignMemberContainer.encode(value, forKey: key)
}
}
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/GeometryCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [GeometryCollection geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8) is a heterogeneous collection of `Geometry` objects that are related.
*/
public struct GeometryCollection: Equatable {
public struct GeometryCollection: Equatable, ForeignMemberContainer {
/// The geometries contained by the geometry collection.
public var geometries: [Geometry]

public var foreignMembers: JSONObject = [:]

/**
Initializes a geometry collection defined by the given geometries.

Expand Down Expand Up @@ -50,11 +52,13 @@ extension GeometryCollection: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let geometries = try container.decode([Geometry].self, forKey: .geometries)
self = .init(geometries: geometries)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.GeometryCollection, forKey: .kind)
try container.encode(geometries, forKey: .geometries)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/LineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [LineString geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.4) is a collection of two or more positions, each position connected to the next position linearly.
*/
public struct LineString: Equatable {
public struct LineString: Equatable, ForeignMemberContainer {
/// The positions at which the line string is located.
public var coordinates: [LocationCoordinate2D]

public var foreignMembers: JSONObject = [:]

/**
Initializes a line string defined by given positions.

Expand Down Expand Up @@ -55,12 +57,14 @@ extension LineString: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode([LocationCoordinate2DCodable].self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.LineString, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}

Expand Down
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/MultiLineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [MultiLineString geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.5) is a collection of `LineString` geometries that are disconnected but related.
*/
public struct MultiLineString: Equatable {
public struct MultiLineString: Equatable, ForeignMemberContainer {
/// The positions at which the multi–line string is located. Each nested array corresponds to one line string.
public var coordinates: [[LocationCoordinate2D]]

public var foreignMembers: JSONObject = [:]

/**
Initializes a multi–line string defined by the given positions.

Expand Down Expand Up @@ -46,11 +48,13 @@ extension MultiLineString: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode([[LocationCoordinate2DCodable]].self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.MultiLineString, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/MultiPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [MultiPoint geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.3) represents a collection of disconnected but related positions.
*/
public struct MultiPoint: Equatable {
public struct MultiPoint: Equatable, ForeignMemberContainer {
/// The positions at which the multipoint is located.
public var coordinates: [LocationCoordinate2D]

public var foreignMembers: JSONObject = [:]

/**
Initializes a multipoint defined by the given positions.

Expand All @@ -35,11 +37,13 @@ extension MultiPoint: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode([LocationCoordinate2DCodable].self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.MultiPoint, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/MultiPolygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [MultiPolygon geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.7) is a collection of `Polygon` geometries that are disconnected but related.
*/
public struct MultiPolygon: Equatable {
public struct MultiPolygon: Equatable, ForeignMemberContainer {
/// The positions at which the multipolygon is located. Each nested array corresponds to one polygon.
public var coordinates: [[[LocationCoordinate2D]]]

public var foreignMembers: JSONObject = [:]

/// The polygon geometries that conceptually form the multipolygon.
public var polygons: [Polygon] {
return coordinates.map { (coordinates) -> Polygon in
Expand Down Expand Up @@ -53,12 +55,14 @@ extension MultiPolygon: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode([[[LocationCoordinate2DCodable]]].self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.MultiPolygon, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}

Expand Down
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/Point.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import CoreLocation
/**
A [Point geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.2) represents a single position.
*/
public struct Point: Equatable {
public struct Point: Equatable, ForeignMemberContainer {
/**
The position at which the point is located.

This property has a plural name for consistency with [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.2). For convenience, it is represented by a `LocationCoordinate2D` instead of a dedicated `Position` type.
*/
public var coordinates: LocationCoordinate2D

public var foreignMembers: JSONObject = [:]

/**
Initializes a point defined by the given position.

Expand All @@ -39,11 +41,13 @@ extension Point: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode(LocationCoordinate2DCodable.self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.Point, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}
6 changes: 5 additions & 1 deletion Sources/Turf/Geometries/Polygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import CoreLocation
/**
A [Polygon geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6) is conceptually a collection of `Ring`s that form a single connected geometry.
*/
public struct Polygon: Equatable {
public struct Polygon: Equatable, ForeignMemberContainer {
/// The positions at which the polygon is located. Each nested array corresponds to one linear ring.
public var coordinates: [[LocationCoordinate2D]]

public var foreignMembers: JSONObject = [:]

/**
Initializes a polygon defined by the given positions.

Expand Down Expand Up @@ -71,12 +73,14 @@ extension Polygon: Codable {
_ = try container.decode(Kind.self, forKey: .kind)
let coordinates = try container.decode([[LocationCoordinate2DCodable]].self, forKey: .coordinates).decodedCoordinates
self = .init(coordinates)
try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Kind.Polygon, forKey: .kind)
try container.encode(coordinates.codableCoordinates, forKey: .coordinates)
try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder)
}
}

Expand Down
16 changes: 13 additions & 3 deletions Sources/Turf/Ring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ public struct Ring {
}
return area
}
}

extension Ring {

/**
* Determines if the given point falls within the ring.
* The optional parameter `ignoreBoundary` will result in the method returning true if the given point
Expand Down Expand Up @@ -105,3 +103,15 @@ extension Ring {
return isInside
}
}

extension Ring: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self = Ring(coordinates: try container.decode([LocationCoordinate2DCodable].self).decodedCoordinates)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(coordinates.codableCoordinates)
}
}
Loading