1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
//
// GeoJSON.swift
// MullvadVPN
//
// Created by pronebird on 25/02/2021.
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
//
import CoreLocation
import Foundation
import MapKit
enum GeoJSON {}
extension GeoJSON {
struct FeatureCollection: Decodable {
let features: [Feature]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
if type == "FeatureCollection" {
features = try container.decode([Feature].self, forKey: .features)
} else {
throw DecodingError.dataCorruptedError(
forKey: .type,
in: container,
debugDescription: "FeatureCollection: Invalid type \(type)"
)
}
}
var mkOverlays: [MKOverlay] {
return features.flatMap { feature -> [MKOverlay] in
// Some tools like mapshaper output empty features after optimizing out the geometry
guard let geometry = feature.geometry else { return [] }
switch geometry {
case let .polygon(polygon):
return polygon.mkPolygons
case let .multiPolygon(multiPolygon):
return multiPolygon.mkPolygons
}
}
}
private enum CodingKeys: String, CodingKey {
case type, features
}
}
struct Feature: Decodable {
let geometry: Geometry?
private enum CodingKeys: String, CodingKey {
case type, geometry
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
if type == "Feature" {
geometry = try container.decodeIfPresent(Geometry.self, forKey: .geometry)
} else {
throw DecodingError.dataCorruptedError(
forKey: .type,
in: container,
debugDescription: "Feature: Invalid type \(type)"
)
}
}
}
enum Geometry: Decodable {
case polygon(Polygon)
case multiPolygon(MultiPolygon)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "Polygon":
self = .polygon(try decoder.singleValueContainer().decode(Polygon.self))
case "MultiPolygon":
self = .multiPolygon(try decoder.singleValueContainer().decode(MultiPolygon.self))
default:
throw DecodingError.dataCorruptedError(
forKey: .type,
in: container,
debugDescription: "Geometry: Unknown type \(type)"
)
}
}
private enum CodingKeys: String, CodingKey {
case type
}
}
struct Polygon: Decodable {
let coordinates: [[[Double]]]
var mkPolygons: [MKPolygon] {
let coords = geoCoordinates
let exteriorCoordinates = coords.first ?? []
let exteriorPolygon = MKPolygon(
coordinates: exteriorCoordinates,
count: exteriorCoordinates.count,
interiorPolygons: nil
)
let interiorPolygons = coords.dropFirst().map { interiorCoords -> MKPolygon in
return MKPolygon(
coordinates: interiorCoords,
count: interiorCoords.count
)
}
return [exteriorPolygon] + interiorPolygons
}
private var geoCoordinates: [[CLLocationCoordinate2D]] {
return coordinates.map { values -> [CLLocationCoordinate2D] in
return values.map { coordinates -> CLLocationCoordinate2D in
return CLLocationCoordinate2D(
latitude: coordinates[1],
longitude: coordinates[0]
)
}
}
}
}
struct MultiPolygon: Decodable {
let coordinates: [[[[Double]]]]
var mkPolygons: [MKOverlay] {
return coordinates.flatMap { values -> [MKPolygon] in
return Polygon(coordinates: values).mkPolygons
}
}
}
static func decodeGeoJSON(_ data: Data) throws -> [MKOverlay] {
return try JSONDecoder()
.decode(GeoJSON.FeatureCollection.self, from: data)
.mkOverlays
}
}
|