Skip to content

Commit dc22d4c

Browse files
committed
✨ Add 3D to GeoJSON
1 parent b8200fa commit dc22d4c

File tree

10 files changed

+296
-19
lines changed

10 files changed

+296
-19
lines changed

Sources/GeoJSON/BoundingBox.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,18 @@ extension BoundingBox2D: BoundingBox {
2222

2323
}
2424

25+
public typealias BoundingBox3D = GeoModels.BoundingBox3D
26+
27+
extension BoundingBox3D: BoundingBox {
28+
29+
public var asAny: AnyBoundingBox { .threeDimensions(self) }
30+
31+
}
32+
2533
public enum AnyBoundingBox: BoundingBox, Hashable, Codable {
2634

2735
case twoDimensions(BoundingBox2D)
36+
case threeDimensions(BoundingBox3D)
2837

2938
public var asAny: AnyBoundingBox { self }
3039

Sources/GeoJSON/GeoJSON+Codable.swift

Lines changed: 143 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ extension Longitude: Codable {
6565

6666
}
6767

68+
extension Altitude: Codable {
69+
70+
public init(from decoder: Decoder) throws {
71+
let container = try decoder.singleValueContainer()
72+
73+
let meters = try container.decode(Double.self)
74+
75+
self.init(meters: meters)
76+
}
77+
78+
public func encode(to encoder: Encoder) throws {
79+
var container = encoder.singleValueContainer()
80+
81+
try container.encode(self.meters.roundedToPlaces(3))
82+
}
83+
84+
}
85+
6886
extension Position2D: Codable {
6987

7088
public init(from decoder: Decoder) throws {
@@ -85,6 +103,28 @@ extension Position2D: Codable {
85103

86104
}
87105

106+
extension Position3D: Codable {
107+
108+
public init(from decoder: Decoder) throws {
109+
var container = try decoder.unkeyedContainer()
110+
111+
let longitude = try container.decode(Longitude.self)
112+
let latitude = try container.decode(Latitude.self)
113+
let altitude = try container.decode(Altitude.self)
114+
115+
self.init(latitude: latitude, longitude: longitude, altitude: altitude)
116+
}
117+
118+
public func encode(to encoder: Encoder) throws {
119+
var container = encoder.unkeyedContainer()
120+
121+
try container.encode(self.longitude)
122+
try container.encode(self.latitude)
123+
try container.encode(self.altitude)
124+
}
125+
126+
}
127+
88128
extension LinearRingCoordinates {
89129

90130
public init(from decoder: Decoder) throws {
@@ -147,34 +187,59 @@ extension AnyGeometry {
147187

148188
let container = try decoder.singleValueContainer()
149189

150-
// FIXME: Fix 2D/3D performance by checking the number of values in `bbox`
190+
// TODO: Fix 2D/3D performance by checking the number of values in `bbox`
151191
switch type {
152192
case .geometryCollection:
153193
let geometryCollection = try container.decode(GeometryCollection.self)
154194
self = .geometryCollection(geometryCollection)
155195
case .point:
156-
// do {
157-
// let point3D = try container.decode(Point3D.self)
158-
// self = .point3D(point3D)
159-
// } catch {
196+
do {
197+
let point3D = try container.decode(Point3D.self)
198+
self = .point3D(point3D)
199+
} catch {
160200
let point2D = try container.decode(Point2D.self)
161201
self = .point2D(point2D)
162-
// }
202+
}
163203
case .multiPoint:
164-
let multiPoint2D = try container.decode(MultiPoint2D.self)
165-
self = .multiPoint2D(multiPoint2D)
204+
do {
205+
let multiPoint3D = try container.decode(MultiPoint3D.self)
206+
self = .multiPoint3D(multiPoint3D)
207+
} catch {
208+
let multiPoint2D = try container.decode(MultiPoint2D.self)
209+
self = .multiPoint2D(multiPoint2D)
210+
}
166211
case .lineString:
167-
let lineString2D = try container.decode(LineString2D.self)
168-
self = .lineString2D(lineString2D)
212+
do {
213+
let lineString3D = try container.decode(LineString3D.self)
214+
self = .lineString3D(lineString3D)
215+
} catch {
216+
let lineString2D = try container.decode(LineString2D.self)
217+
self = .lineString2D(lineString2D)
218+
}
169219
case .multiLineString:
170-
let multiLineString2D = try container.decode(MultiLineString2D.self)
171-
self = .multiLineString2D(multiLineString2D)
220+
do {
221+
let multiLineString3D = try container.decode(MultiLineString3D.self)
222+
self = .multiLineString3D(multiLineString3D)
223+
} catch {
224+
let multiLineString2D = try container.decode(MultiLineString2D.self)
225+
self = .multiLineString2D(multiLineString2D)
226+
}
172227
case .polygon:
173-
let polygon2D = try container.decode(Polygon2D.self)
174-
self = .polygon2D(polygon2D)
228+
do {
229+
let polygon3D = try container.decode(Polygon3D.self)
230+
self = .polygon3D(polygon3D)
231+
} catch {
232+
let polygon2D = try container.decode(Polygon2D.self)
233+
self = .polygon2D(polygon2D)
234+
}
175235
case .multiPolygon:
176-
let multiPolygon2D = try container.decode(MultiPolygon2D.self)
177-
self = .multiPolygon2D(multiPolygon2D)
236+
do {
237+
let multiPolygon3D = try container.decode(MultiPolygon3D.self)
238+
self = .multiPolygon3D(multiPolygon3D)
239+
} catch {
240+
let multiPolygon2D = try container.decode(MultiPolygon2D.self)
241+
self = .multiPolygon2D(multiPolygon2D)
242+
}
178243
}
179244
}
180245

@@ -184,6 +249,7 @@ extension AnyGeometry {
184249
switch self {
185250
case .geometryCollection(let geometryCollection):
186251
try container.encode(geometryCollection)
252+
187253
case .point2D(let point2D):
188254
try container.encode(point2D)
189255
case .multiPoint2D(let multiPoint2D):
@@ -196,6 +262,19 @@ extension AnyGeometry {
196262
try container.encode(polygon2D)
197263
case .multiPolygon2D(let multiPolygon2D):
198264
try container.encode(multiPolygon2D)
265+
266+
case .point3D(let point3D):
267+
try container.encode(point3D)
268+
case .multiPoint3D(let multiPoint3D):
269+
try container.encode(multiPoint3D)
270+
case .lineString3D(let lineString3D):
271+
try container.encode(lineString3D)
272+
case .multiLineString3D(let multiLineString3D):
273+
try container.encode(multiLineString3D)
274+
case .polygon3D(let polygon3D):
275+
try container.encode(polygon3D)
276+
case .multiPolygon3D(let multiPolygon3D):
277+
try container.encode(multiPolygon3D)
199278
}
200279
}
201280

@@ -228,13 +307,57 @@ extension BoundingBox2D {
228307

229308
}
230309

310+
extension BoundingBox3D {
311+
312+
public init(from decoder: Decoder) throws {
313+
var container = try decoder.unkeyedContainer()
314+
315+
let westLongitude = try container.decode(Longitude.self)
316+
let southLatitude = try container.decode(Latitude.self)
317+
let lowAltitude = try container.decode(Altitude.self)
318+
let eastLongitude = try container.decode(Longitude.self)
319+
let northLatitude = try container.decode(Latitude.self)
320+
let highAltitude = try container.decode(Altitude.self)
321+
322+
self.init(
323+
southWestLow: Coordinate3D(
324+
latitude: southLatitude,
325+
longitude: westLongitude,
326+
altitude: lowAltitude
327+
),
328+
northEastHigh: Coordinate3D(
329+
latitude: northLatitude,
330+
longitude: eastLongitude,
331+
altitude: highAltitude
332+
)
333+
)
334+
}
335+
336+
public func encode(to encoder: Encoder) throws {
337+
var container = encoder.unkeyedContainer()
338+
339+
try container.encode(self.twoDimensions.westLongitude)
340+
try container.encode(self.twoDimensions.southLatitude)
341+
try container.encode(self.lowAltitude)
342+
try container.encode(self.twoDimensions.eastLongitude)
343+
try container.encode(self.twoDimensions.northLatitude)
344+
try container.encode(self.highAltitude)
345+
}
346+
347+
}
348+
231349
extension AnyBoundingBox {
232350

233351
public init(from decoder: Decoder) throws {
234352
let container = try decoder.singleValueContainer()
235353

236-
let boundingBox2D = try container.decode(BoundingBox2D.self)
237-
self = .twoDimensions(boundingBox2D)
354+
do {
355+
let boundingBox3D = try container.decode(BoundingBox3D.self)
356+
self = .threeDimensions(boundingBox3D)
357+
} catch {
358+
let boundingBox2D = try container.decode(BoundingBox2D.self)
359+
self = .twoDimensions(boundingBox2D)
360+
}
238361
}
239362

240363
public func encode(to encoder: Encoder) throws {
@@ -243,6 +366,8 @@ extension AnyBoundingBox {
243366
switch self {
244367
case .twoDimensions(let boundingBox2D):
245368
try container.encode(boundingBox2D)
369+
case .threeDimensions(let boundingBox3D):
370+
try container.encode(boundingBox3D)
246371
}
247372
}
248373

Sources/GeoJSON/Geometries/LineString.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,27 @@ public struct LineString2D: LineString {
3838
}
3939

4040
}
41+
42+
public struct LineString3D: LineString {
43+
44+
public typealias Position = Position3D
45+
46+
public static var geometryType: GeoJSON.`Type`.Geometry { .lineString }
47+
48+
public var coordinates: Coordinates
49+
50+
public var asAnyGeometry: AnyGeometry { .lineString3D(self) }
51+
52+
public init(coordinates: Coordinates) {
53+
self.coordinates = coordinates
54+
}
55+
56+
public init?(coordinates: [Position3D]) {
57+
guard let coordinates = NonEmpty(rawValue: coordinates)
58+
.flatMap(NonEmpty.init(rawValue:))
59+
else { return nil }
60+
61+
self.init(coordinates: coordinates)
62+
}
63+
64+
}

Sources/GeoJSON/Geometries/MultiLineString.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,33 @@ public struct MultiLineString2D: MultiLineString {
4444
}
4545

4646
}
47+
48+
public struct MultiLineString3D: MultiLineString {
49+
50+
public typealias LineString = LineString3D
51+
52+
public static var geometryType: GeoJSON.`Type`.Geometry { .multiLineString }
53+
54+
public var coordinates: Coordinates
55+
56+
public var asAnyGeometry: AnyGeometry { .multiLineString3D(self) }
57+
58+
public init(coordinates: Coordinates) {
59+
self.coordinates = coordinates
60+
}
61+
62+
public init?(coordinates: [[Position3D]]) {
63+
var coord1 = [LineString3D.Coordinates]()
64+
65+
for coord2 in coordinates {
66+
guard let coord3 = NonEmpty(rawValue: coord2)
67+
.flatMap(NonEmpty.init(rawValue:))
68+
else { return nil }
69+
70+
coord1.append(coord3)
71+
}
72+
73+
self.init(coordinates: coord1)
74+
}
75+
76+
}

Sources/GeoJSON/Geometries/MultiPoint.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,19 @@ public struct MultiPoint2D: MultiPoint {
2828
}
2929

3030
}
31+
32+
public struct MultiPoint3D: MultiPoint {
33+
34+
public typealias Point = Point3D
35+
36+
public static var geometryType: GeoJSON.`Type`.Geometry { .multiPoint }
37+
38+
public var coordinates: Coordinates
39+
40+
public var asAnyGeometry: AnyGeometry { .multiPoint3D(self) }
41+
42+
public init(coordinates: Coordinates) {
43+
self.coordinates = coordinates
44+
}
45+
46+
}

Sources/GeoJSON/Geometries/MultiPolygon.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,19 @@ public struct MultiPolygon2D: MultiPolygon {
2828
}
2929

3030
}
31+
32+
public struct MultiPolygon3D: MultiPolygon {
33+
34+
public typealias Polygon = Polygon3D
35+
36+
public static var geometryType: GeoJSON.`Type`.Geometry { .multiPolygon }
37+
38+
public var coordinates: Coordinates
39+
40+
public var asAnyGeometry: AnyGeometry { .multiPolygon3D(self) }
41+
42+
public init(coordinates: Coordinates) {
43+
self.coordinates = coordinates
44+
}
45+
46+
}

Sources/GeoJSON/Geometries/Point.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,19 @@ public struct Point2D: Point {
2828
}
2929

3030
}
31+
32+
public struct Point3D: Point {
33+
34+
public typealias Position = Position3D
35+
36+
public static var geometryType: GeoJSON.`Type`.Geometry { .point }
37+
38+
public var coordinates: Coordinates
39+
40+
public var asAnyGeometry: AnyGeometry { .point3D(self) }
41+
42+
public init(coordinates: Coordinates) {
43+
self.coordinates = coordinates
44+
}
45+
46+
}

Sources/GeoJSON/Geometries/Polygon.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extension LinearRingCoordinates: ExpressibleByArrayLiteral {
5555

5656
}
5757

58-
/// A polygon / linear ring.
58+
/// A 2D polygon / linear ring.
5959
public struct Polygon2D: Polygon {
6060

6161
public typealias Point = Point2D
@@ -71,3 +71,20 @@ public struct Polygon2D: Polygon {
7171
}
7272

7373
}
74+
75+
/// A 3D polygon / linear ring.
76+
public struct Polygon3D: Polygon {
77+
78+
public typealias Point = Point3D
79+
80+
public static var geometryType: GeoJSON.`Type`.Geometry { .polygon }
81+
82+
public var coordinates: Coordinates
83+
84+
public var asAnyGeometry: AnyGeometry { .polygon3D(self) }
85+
86+
public init(coordinates: Coordinates) {
87+
self.coordinates = coordinates
88+
}
89+
90+
}

0 commit comments

Comments
 (0)