Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add "id" to Feature
Browse files Browse the repository at this point in the history
RemiBardon committed Feb 9, 2022
1 parent c3f4594 commit 0aa9cda
Showing 9 changed files with 127 additions and 23 deletions.
10 changes: 0 additions & 10 deletions Sources/GeoJSON/FeatureProperties.swift

This file was deleted.

9 changes: 6 additions & 3 deletions Sources/GeoJSON/GeoJSON+Codable.swift
Original file line number Diff line number Diff line change
@@ -375,12 +375,13 @@ extension AnyBoundingBox {

fileprivate enum FeatureCodingKeys: String, CodingKey {
case geoJSONType = "type"
case geometry, properties, bbox
case id, geometry, properties, bbox
}

extension Feature {

public init(from decoder: Decoder) throws {
print(String(describing: ID.self))
let container = try decoder.container(keyedBy: FeatureCodingKeys.self)

let type = try container.decode(GeoJSON.`Type`.self, forKey: .geoJSONType)
@@ -391,16 +392,18 @@ extension Feature {
))
}

let id = try container.decodeIfPresent(ID.self, forKey: .id)
let geometry = try container.decodeIfPresent(Geometry.self, forKey: .geometry)
let properties = try container.decode(Properties.self, forKey: .properties)

self.init(geometry: geometry, properties: properties)
self.init(id: id, geometry: geometry, properties: properties)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: FeatureCodingKeys.self)

try container.encode(Self.geoJSONType, forKey: .geoJSONType)
try container.encodeIfPresent(self.id, forKey: .id)
try container.encodeIfPresent(self.geometry, forKey: .geometry)
try container.encode(self.properties, forKey: .properties)
// TODO: Create GeoJSONEncoder that allows setting "export bboxes" to a boolean value
@@ -429,7 +432,7 @@ extension FeatureCollection {
}

let features = try container.decodeIfPresent(
[Feature<Geometry, Properties>].self,
[Feature<ID, Geometry, Properties>].self,
forKey: .features
) ?? []

9 changes: 9 additions & 0 deletions Sources/GeoJSON/Helpers/NonID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// NonID.swift
// GeoSwift
//
// Created by Rémi Bardon on 09/02/2022.
// Copyright © 2022 Rémi Bardon. All rights reserved.
//

public typealias NonID = Bool
36 changes: 33 additions & 3 deletions Sources/GeoJSON/Objects/Feature.swift
Original file line number Diff line number Diff line change
@@ -8,23 +8,53 @@

/// A [GeoJSON Feature](https://datatracker.ietf.org/doc/html/rfc7946#section-3.2).
public struct Feature<
ID: Codable,
Geometry: GeoJSON.Geometry & Codable,
Properties: GeoJSON.FeatureProperties
Properties: Codable
>: CodableObject {

public static var geoJSONType: GeoJSON.`Type` { .feature }

public var bbox: Geometry.BoundingBox? { geometry?.bbox }

public var id: ID?
public var geometry: Geometry?
/// The `"properties"` field of a [GeoJSON Feature](https://datatracker.ietf.org/doc/html/rfc7946#section-3.2).
public var properties: Properties

public init(geometry: Geometry?, properties: Properties) {
public init(id: ID?, geometry: Geometry?, properties: Properties) {
self.id = id
self.geometry = geometry
self.properties = properties
}

public init(geometry: Geometry?, properties: Properties) where ID == NonID {
self.id = nil
self.geometry = geometry
self.properties = properties
}

}

extension Feature: Identifiable where ID: Hashable {}
extension Feature: Equatable where ID: Equatable, Properties: Equatable {

public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
&& lhs.geometry == rhs.geometry
&& lhs.properties == rhs.properties
}

}
extension Feature: Hashable where ID: Hashable, Properties: Hashable {

public func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(geometry)
hasher.combine(properties)
}

}

/// A (half) type-erased ``Feature``.
public typealias AnyFeature<Properties: GeoJSON.FeatureProperties> = Feature<AnyGeometry, Properties>
public typealias AnyFeature<Properties: Codable> = Feature<NonID, AnyGeometry, Properties>
25 changes: 21 additions & 4 deletions Sources/GeoJSON/Objects/FeatureCollection.swift
Original file line number Diff line number Diff line change
@@ -8,20 +8,37 @@

/// A [GeoJSON FeatureCollection](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3).
public struct FeatureCollection<
ID: Codable,
Geometry: GeoJSON.Geometry & Codable,
Properties: GeoJSON.FeatureProperties
Properties: Codable
>: CodableObject {

public static var geoJSONType: GeoJSON.`Type` { .featureCollection }

public var features: [Feature<Geometry, Properties>]
public var features: [Feature<ID, Geometry, Properties>]

// FIXME: Fix bounding box
public var bbox: AnyBoundingBox? { nil }

}

extension FeatureCollection: Equatable where ID: Equatable, Properties: Equatable {

public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.features == rhs.features
}

}

extension FeatureCollection: Hashable where ID: Hashable, Properties: Hashable {

public func hash(into hasher: inout Hasher) {
hasher.combine(features)
}

}

/// A (half) type-erased ``FeatureCollection``.
public typealias AnyFeatureCollection<
Properties: GeoJSON.FeatureProperties
> = FeatureCollection<AnyGeometry, Properties>
Properties: Codable
> = FeatureCollection<NonID, AnyGeometry, Properties>
2 changes: 1 addition & 1 deletion Sources/GeoJSON/Objects/Geometry.swift
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
import Turf

/// A [GeoJSON Geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1).
public protocol Geometry: GeoJSON.Object {
public protocol Geometry: GeoJSON.Object, Hashable {

var bbox: BoundingBox? { get }

2 changes: 1 addition & 1 deletion Sources/GeoJSON/Objects/Object.swift
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
//

/// A [GeoJSON Object](https://datatracker.ietf.org/doc/html/rfc7946#section-3).
public protocol Object: Hashable {
public protocol Object {

associatedtype BoundingBox: GeoJSON.BoundingBox

29 changes: 28 additions & 1 deletion Tests/GeoJSONTests/GeoJSON+DecodableTests.swift
Original file line number Diff line number Diff line change
@@ -170,7 +170,7 @@ final class GeoJSONDecodableTests: XCTestCase {
].joined()

let data: Data = try XCTUnwrap(string.data(using: .utf8))
let feature = try JSONDecoder().decode(Feature<Point2D, FeatureProperties>.self, from: data)
let feature = try JSONDecoder().decode(Feature<NonID, Point2D, FeatureProperties>.self, from: data)

let expected: Feature = Feature(
geometry: Point2D(coordinates: .nantes),
@@ -179,6 +179,33 @@ final class GeoJSONDecodableTests: XCTestCase {
XCTAssertEqual(feature, expected)
}

func testFeature2DWithIDDecode() throws {
struct FeatureProperties: Hashable, Codable {}

let string: String = [
"{",
"\"type\":\"Feature\",",
"\"id\":\"feature_id\",",
"\"geometry\":{",
"\"type\":\"Point\",",
"\"coordinates\":[-1.55366,47.21881]",
"},",
"\"properties\":{},",
"\"bbox\":[-1.55366,47.21881,-1.55366,47.21881]",
"}",
].joined()

let data: Data = try XCTUnwrap(string.data(using: .utf8))
let feature = try JSONDecoder().decode(Feature<String, Point2D, FeatureProperties>.self, from: data)

let expected: Feature = Feature(
id: "feature_id",
geometry: Point2D(coordinates: .nantes),
properties: FeatureProperties()
)
XCTAssertEqual(feature, expected)
}

// MARK: Real-world use cases

func testDecodeFeatureProperties() throws {
28 changes: 28 additions & 0 deletions Tests/GeoJSONTests/GeoJSON+EncodableTests.swift
Original file line number Diff line number Diff line change
@@ -163,4 +163,32 @@ final class GeoJSONEncodableTests: XCTestCase {
XCTAssertEqual(string, expected)
}

func testFeature2DWithIDEncode() throws {
struct FeatureProperties: Hashable, Codable {}

let feature: Feature = Feature(
id: "feature_id",
geometry: Point2D(coordinates: .nantes),
properties: FeatureProperties()
)
let data: Data = try JSONEncoder().encode(feature)
let string: String = try XCTUnwrap(String(data: data, encoding: .utf8))

let expected: String = [
"{",
// For some reason, `"id"` goes here 🤷
"\"id\":\"feature_id\",",
// For some reason, `"properties"` goes here 🤷
"\"properties\":{},",
"\"type\":\"Feature\",",
"\"geometry\":{",
"\"type\":\"Point\",",
"\"coordinates\":[-1.55366,47.21881]",
"},",
"\"bbox\":[-1.55366,47.21881,-1.55366,47.21881]",
"}",
].joined()
XCTAssertEqual(string, expected)
}

}

0 comments on commit 0aa9cda

Please sign in to comment.