-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds Collection<GeoJSON.Position>.convexHull() (#7)
Adds convexHull() Also: - SPM update to get closed polygon rings - Refactoring
- Loading branch information
Showing
21 changed files
with
1,386 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// Turf+ConvexHull.swift | ||
// | ||
// | ||
// Created by Adrian Schönig on 14/6/2022. | ||
// | ||
|
||
import Foundation | ||
|
||
import GeoJSONKit | ||
|
||
extension Collection where Element == GeoJSON.Position { | ||
/// Calculates the convex hull of a given sequence of positions. | ||
/// | ||
/// - Complexity: O(*n* log *n*), where *n* is the count of `points`. | ||
/// | ||
/// - Returns: The convex hull of this sequence as a polygon | ||
public func convexHull() -> GeoJSON.Polygon { | ||
let positions = AndrewsMonotoneChain.convexHull(self).map { $0.removingAltitude } | ||
return .init(exterior: .init(positions: positions)) | ||
} | ||
} | ||
|
||
extension GeoJSON { | ||
/// Calculates the convex hull of all the elements of this GeoJSON | ||
/// | ||
/// - Returns: The convex hull of this GeoJSON as a polygon | ||
public func convexHull() -> GeoJSON.Polygon { | ||
return positions.convexHull() | ||
} | ||
} | ||
|
||
extension GeoJSON.Position { | ||
fileprivate var removingAltitude: GeoJSON.Position { | ||
guard altitude != nil else { return self } | ||
var updated = self | ||
updated.altitude = nil | ||
return updated | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// | ||
// GeoJSON+Helpers.swift | ||
// | ||
// | ||
// Created by Adrian Schönig on 14/6/2022. | ||
// | ||
|
||
import GeoJSONKit | ||
|
||
extension GeoJSON.GeometryObject { | ||
public var geometries: [GeoJSON.Geometry] { | ||
switch self { | ||
case .single(let geo): return [geo] | ||
case .multi(let geos): return geos | ||
case .collection(let geoObjects): return geoObjects.flatMap(\.geometries) | ||
} | ||
} | ||
|
||
public var positions: [GeoJSON.Position] { | ||
geometries.flatMap(\.positions) | ||
} | ||
} | ||
|
||
extension GeoJSON.Geometry { | ||
public var positions: [GeoJSON.Position] { | ||
switch self { | ||
case .point(let position): return [position] | ||
case .lineString(let line): return line.positions | ||
case .polygon(let polygon): | ||
// Ignore the interior positions as the purpose of this is getting | ||
// bounding boxes, convex hull or alike | ||
return polygon.exterior.positions | ||
} | ||
} | ||
} | ||
|
||
extension GeoJSON { | ||
public var geometries: [GeoJSON.Geometry] { | ||
switch object { | ||
case .feature(let feature): | ||
return feature.geometry.geometries | ||
case .featureCollection(let features): | ||
return features.flatMap(\.geometry.geometries) | ||
case .geometry(let geometry): | ||
return geometry.geometries | ||
} | ||
} | ||
|
||
public var positions: [GeoJSON.Position] { | ||
geometries.flatMap(\.positions) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// | ||
// MonotoneChain.swift | ||
// | ||
// | ||
// Created by Adrian Schönig on 14/6/2022. | ||
// | ||
|
||
import Foundation | ||
|
||
import GeoJSONKit | ||
|
||
// Adopted from https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain | ||
struct AndrewsMonotoneChain { | ||
private static func cross(_ o: GeoJSON.Position, _ a: GeoJSON.Position, _ b: GeoJSON.Position) -> Double { | ||
let lhs = (a.x - o.x) * (b.y - o.y) | ||
let rhs = (a.y - o.y) * (b.x - o.x) | ||
return lhs - rhs | ||
} | ||
|
||
/// Calculate and return the convex hull of a given sequence of points. | ||
/// | ||
/// - Remark: Implements Andrew’s monotone chain convex hull algorithm. | ||
/// | ||
/// - Complexity: O(*n* log *n*), where *n* is the count of `points`. | ||
/// | ||
/// - Parameter points: A sequence of `GeoJSON.Position` elements. | ||
/// | ||
/// - Returns: An array containing the convex hull of `points`, ordered | ||
/// lexicographically from the smallest coordinates to the largest, | ||
/// turning counterclockwise. | ||
/// | ||
static func convexHull<Source>(_ points: Source) -> [GeoJSON.Position] | ||
where Source : Collection, | ||
Source.Element == GeoJSON.Position | ||
{ | ||
// Exit early if there aren’t enough points to work with. | ||
guard points.count > 1 else { return Array(points) } | ||
|
||
// Create storage for the lower and upper hulls. | ||
var lower = [GeoJSON.Position]() | ||
var upper = [GeoJSON.Position]() | ||
|
||
// Sort points in lexicographical order. | ||
let points = points.sorted { a, b in | ||
a.x < b.x || a.x == b.x && a.y < b.y | ||
} | ||
|
||
// Construct the lower hull. | ||
for point in points { | ||
while lower.count >= 2 { | ||
let a = lower[lower.count - 2] | ||
let b = lower[lower.count - 1] | ||
if cross(a, b, point) > 0 { break } | ||
lower.removeLast() | ||
} | ||
lower.append(point) | ||
} | ||
|
||
// Construct the upper hull. | ||
for point in points.lazy.reversed() { | ||
while upper.count >= 2 { | ||
let a = upper[upper.count - 2] | ||
let b = upper[upper.count - 1] | ||
if cross(a, b, point) > 0 { break } | ||
upper.removeLast() | ||
} | ||
upper.append(point) | ||
} | ||
|
||
// Remove each array’s last point, as it’s the same as the first point | ||
// in the opposite array, respectively. | ||
lower.removeLast() | ||
upper.removeLast() | ||
|
||
// Join the arrays to form the convex hull. | ||
return lower + upper | ||
} | ||
} | ||
|
||
fileprivate extension GeoJSON.Position { | ||
var x: Double { longitude } | ||
var y: Double { latitude } | ||
} |
Oops, something went wrong.