@@ -3,7 +3,205 @@ import CoreLocation
33#endif
44import Foundation
55
6- // TODO: Port from https://github.com/Turfjs/turf/blob/master/packages/turf-buffer
7- // and https://github.com/DenisCarriere/turf-jsts/tree/master/src/org/locationtech/jts/operation/buffer
6+ /// Line end styles for ```GeoJson.buffered(by:lineEndStyle:steps:formUnion:)```.
7+ public enum LineEndStyle {
88
9- // TODO
9+ /// Line ends will be flat.
10+ case flat
11+
12+ /// Line ends will be rounded.
13+ case round
14+
15+ }
16+
17+ extension GeoJson {
18+
19+ // TODO: Antimeridian Cutting
20+ // TODO: formUnion (= dissolve)
21+
22+ /// Returns the receiver with a buffer.
23+ ///
24+ /// - Parameters:
25+ /// - distance: The buffer distance, in meters
26+ /// - lineCapStyle: Controls how line ends will be drawn (default round)
27+ /// - steps: The number of steps for the circles (default 64)
28+ /// - formUnion: Whether to combine all overlapping buffers into one Polygon (default true)
29+ public func buffered(
30+ by distance: Double ,
31+ lineEndStyle: LineEndStyle = . round,
32+ steps: Int = 64 ,
33+ formUnion: Bool = true
34+ ) -> MultiPolygon ? {
35+ guard distance > 0.0 else { return nil }
36+
37+ switch self {
38+ // Point
39+ case let point as Point :
40+ guard let circle = point. circle ( radius: distance, steps: steps) else { return nil }
41+ return MultiPolygon ( [ circle] )
42+
43+ // MultiPoint
44+ case let multiPoint as MultiPoint :
45+ let circles = multiPoint
46+ . points
47+ . compactMap ( { $0. circle ( radius: distance, steps: steps) } )
48+ guard circles. isNotEmpty else { return nil }
49+
50+ // TODO: formUnion
51+
52+ return MultiPolygon ( circles)
53+
54+ // LineString
55+ case let lineString as LineString :
56+ let bufferedSegments = lineString
57+ . lineSegments
58+ . compactMap ( { $0. buffered ( by: distance, lineEndStyle: . flat) ? . polygons. first } )
59+ guard var multiPolygon = MultiPolygon ( bufferedSegments) else { return nil }
60+
61+ var bufferCoordinates = lineString. coordinates
62+ guard bufferCoordinates. count >= 2 else { return multiPolygon }
63+
64+ if lineEndStyle == . flat {
65+ bufferCoordinates. removeFirst ( )
66+ bufferCoordinates. removeLast ( )
67+ }
68+
69+ for coordinate in bufferCoordinates {
70+ guard let circle = coordinate. circle ( radius: distance, steps: steps) else { continue }
71+ multiPolygon. appendPolygon ( circle)
72+ }
73+
74+ // TODO: formUnion
75+
76+ return multiPolygon
77+
78+ // MultiLineString
79+ case let multiLineString as MultiLineString :
80+ let bufferedLines = multiLineString
81+ . lineStrings
82+ . compactMap ( { $0. buffered ( by: distance, lineEndStyle: lineEndStyle, steps: steps, formUnion: formUnion) } )
83+ guard bufferedLines. isNotEmpty else { return nil }
84+
85+ // TODO: formUnion
86+
87+ return MultiPolygon ( bufferedLines. map ( \. polygons) . flatMap ( { $0 } ) )
88+
89+ // Polygon
90+ case let polygon as Polygon :
91+ let bufferCoordinates = polygon. allCoordinates
92+ let bufferedSegments = polygon
93+ . lineSegments
94+ . compactMap ( {
95+ $0. buffered ( by: distance, lineEndStyle: . flat) ? . polygons. first
96+ } )
97+ guard bufferCoordinates. count >= 2 ,
98+ var multiPolygon = MultiPolygon ( bufferedSegments)
99+ else { return nil }
100+
101+ for coordinate in bufferCoordinates {
102+ guard let circle = coordinate. circle ( radius: distance, steps: steps) else { continue }
103+ multiPolygon. appendPolygon ( circle)
104+ }
105+
106+ multiPolygon. appendPolygon ( polygon)
107+
108+ // TODO: formUnion
109+
110+ return multiPolygon
111+
112+ // MultiPolygon
113+ case let multiPolygon as MultiPolygon :
114+ let bufferedPolygons = multiPolygon
115+ . polygons
116+ . compactMap ( { $0. buffered ( by: distance, lineEndStyle: lineEndStyle, steps: steps, formUnion: formUnion) } )
117+ guard bufferedPolygons. isNotEmpty else { return nil }
118+
119+ // TODO: formUnion
120+
121+ return MultiPolygon ( bufferedPolygons. map ( \. polygons) . flatMap ( { $0 } ) )
122+
123+ // GeometryCollection
124+ case let geometryCollection as GeometryCollection :
125+ let bufferPolygons = geometryCollection
126+ . geometries
127+ . compactMap ( {
128+ $0. buffered ( by: distance, lineEndStyle: lineEndStyle, steps: steps, formUnion: formUnion) ? . polygons
129+ } )
130+ . flatMap ( { $0 } )
131+ guard bufferPolygons. isNotEmpty else { return nil }
132+
133+ // TODO: formUnion
134+
135+ return MultiPolygon ( bufferPolygons)
136+
137+ // Feature
138+ case let feature as Feature :
139+ return feature. geometry. buffered ( by: distance, lineEndStyle: lineEndStyle, steps: steps, formUnion: formUnion)
140+
141+ // FeatureCollection
142+ case let featureCollection as FeatureCollection :
143+ let bufferPolygons = featureCollection
144+ . features
145+ . compactMap ( {
146+ $0. geometry. buffered ( by: distance, lineEndStyle: lineEndStyle, steps: steps, formUnion: formUnion) ? . polygons
147+ } )
148+ . flatMap ( { $0 } )
149+ guard bufferPolygons. isNotEmpty else { return nil }
150+
151+ // TODO: formUnion
152+
153+ return MultiPolygon ( bufferPolygons)
154+
155+ // Can't happen
156+ default :
157+ return nil
158+ }
159+ }
160+
161+ }
162+
163+ extension LineSegment {
164+
165+ /// Returns the line segment with a buffer.
166+ ///
167+ /// - Parameters:
168+ /// - distance: The buffer distance, in meters
169+ /// - lineCapStyle: Controls how line ends will be drawn (default round)
170+ /// - steps: The number of steps for the circles (default 64)
171+ /// - formUnion: Whether to combine all overlapping buffers into one Polygon (default true)
172+ public func buffered(
173+ by distance: Double ,
174+ lineEndStyle: LineEndStyle = . round,
175+ steps: Int = 64 ,
176+ formUnion: Bool = true
177+ ) -> MultiPolygon ? {
178+ guard distance > 0.0 else { return nil }
179+
180+ let firstBearing = self . bearing
181+ let leftBearing = ( firstBearing - 90.0 ) . truncatingRemainder ( dividingBy: 360.0 )
182+ let rightBearing = ( firstBearing + 90.0 ) . truncatingRemainder ( dividingBy: 360.0 )
183+
184+ let corners = [
185+ first. destination ( distance: distance, bearing: leftBearing) ,
186+ second. destination ( distance: distance, bearing: leftBearing) ,
187+ second. destination ( distance: distance, bearing: rightBearing) ,
188+ first. destination ( distance: distance, bearing: rightBearing) ,
189+ first. destination ( distance: distance, bearing: leftBearing) ,
190+ ]
191+
192+ guard var multiPolygon = MultiPolygon ( [ [ corners] ] ) else { return nil }
193+
194+ if lineEndStyle == . round,
195+ let firstCircle = first. circle ( radius: distance, steps: steps) ,
196+ let secondCircle = second. circle ( radius: distance, steps: steps)
197+ {
198+ multiPolygon. appendPolygon ( firstCircle)
199+ multiPolygon. appendPolygon ( secondCircle)
200+ }
201+
202+ // TODO: formUnion
203+
204+ return multiPolygon
205+ }
206+
207+ }
0 commit comments