Skip to content

Commit

Permalink
Merge pull request #167 from mapbox/vk/141-linestring-intersection
Browse files Browse the repository at this point in the history
Linestring.intersection method
  • Loading branch information
Udumft authored Nov 10, 2021
2 parents 1806393 + 16049b2 commit 43d144f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Turf.js | Turf for Swift
[turf-helpers#radiansToDegrees](https://turfjs.org/docs/#radiansToDegrees) | `CLLocationDegrees.toDegrees()`<br>`LocationDegrees.toDegrees()` on Linux
[turf-helpers#convertLength](https://turfjs.org/docs/#convertLength)<br>[turf-helpers#convertArea](https://turfjs.org/docs/#convertArea) | `Measurement.converted(to:)`
[turf-length#length](https://turfjs.org/docs/#length) | `LineString.distance(from:to:)`
[turf-line-intersect#lineIntersect](https://turfjs.org/docs/#lineIntersect) | `intersection(_:_:)`
[turf-line-intersect#lineIntersect](https://turfjs.org/docs/#lineIntersect) | `LineString.intersections(with:)`
[turf-line-slice#lineSlice](https://turfjs.org/docs/#lineSlice) | `LineString.sliced(from:to:)`
[turf-line-slice-along#lineSliceAlong](https://turfjs.org/docs/#lineSliceAlong) | `LineString.trimmed(from:to:)`
[turf-midpoint#midpoint](https://turfjs.org/docs/#midpoint) | `mid(_:_:)`
Expand Down
46 changes: 45 additions & 1 deletion Sources/Turf/Geometries/LineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public struct LineString: Equatable {
public init(_ ring: Ring) {
self.coordinates = ring.coordinates
}

/**
Representation of current `LineString` as an array of `LineSegment`s.
*/
var segments: [LineSegment] {
return zip(coordinates.dropLast(), coordinates.dropFirst()).map { LineSegment($0.0, $0.1) }
}
}

extension LineString: Codable {
Expand Down Expand Up @@ -308,7 +315,7 @@ extension LineString {
let direction = segment.0.direction(to: segment.1)
let perpendicularPoint1 = coordinate.coordinate(at: maxDistance, facing: direction + 90)
let perpendicularPoint2 = coordinate.coordinate(at: maxDistance, facing: direction - 90)
let intersectionPoint = intersection((perpendicularPoint1, perpendicularPoint2), segment)
let intersectionPoint = Turf.intersection((perpendicularPoint1, perpendicularPoint2), segment)
let intersectionDistance: LocationDistance? = intersectionPoint != nil ? coordinate.distance(to: intersectionPoint!) : nil

if distances.0 < closestDistance ?? .greatestFiniteMagnitude {
Expand Down Expand Up @@ -364,4 +371,41 @@ extension LineString {
// Ported from https://github.com/Turfjs/turf/blob/4e8342acb1dbd099f5e91c8ee27f05fb2647ee1b/packages/turf-simplify/lib/simplify.js
coordinates = Simplifier.simplify(coordinates, tolerance: tolerance, highestQuality: highestQuality)
}

/**
Returns all intersections with another `LineString`.

This function is roughly equivalent to the [turf-line-intersect](https://turfjs.org/docs/#lineIntersect) package of Turf.js ([source code](https://github.com/Turfjs/turf/tree/master/packages/turf-line-intersect/)). Order of found intersections is not determined.

You can also use `Turf.intersection(_:, _:)` if you need to find intersection of individual `LineSegment`s.

- seealso: `Turf.intersection(_:, _:)`
*/
public func intersections(with line: LineString) -> [LocationCoordinate2D] {
var intersections = Set<HashableCoordinate>()
for segment1 in segments {
for segment2 in line.segments {
if let intersection = Turf.intersection(LineSegment(segment1.0, segment1.1),
LineSegment(segment2.0, segment2.1)) {
intersections.insert(.init(intersection))
}
}
}
return intersections.map { $0.locationCoordinate }
}

private struct HashableCoordinate: Hashable {
let latitude: Double
let longitude: Double

var locationCoordinate: LocationCoordinate2D {
return LocationCoordinate2D(latitude: latitude,
longitude: longitude)
}

init(_ coordinate: LocationCoordinate2D) {
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
}
6 changes: 4 additions & 2 deletions Sources/Turf/Turf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public typealias LineSegment = (LocationCoordinate2D, LocationCoordinate2D)
Returns the intersection of two line segments.

This function is roughly equivalent to the [turf-line-intersect](https://turfjs.org/docs/#lineIntersect) package of Turf.js ([source code](https://github.com/Turfjs/turf/tree/master/packages/turf-line-intersect/)), except that it only accepts individual line segments instead of whole line strings.

- seealso: `LineString.intersection(with:)`
*/
public func intersection(_ line1: LineSegment, _ line2: LineSegment) -> LocationCoordinate2D? {
// Ported from https://github.com/Turfjs/turf/blob/142e137ce0c758e2825a260ab32b24db0aa19439/packages/turf-point-on-line/index.js, in turn adapted from http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
Expand All @@ -35,9 +37,9 @@ public func intersection(_ line1: LineSegment, _ line2: LineSegment) -> Location
longitude: line1.0.longitude + a * (line1.1.longitude - line1.0.longitude))

/// True if line 1 is finite and line 2 is infinite.
let intersectsWithLine1 = a > 0 && a < 1
let intersectsWithLine1 = a >= 0 && a <= 1
/// True if line 2 is finite and line 1 is infinite.
let intersectsWithLine2 = b > 0 && b < 1
let intersectsWithLine2 = b >= 0 && b <= 1
return intersectsWithLine1 && intersectsWithLine2 ? intersection : nil
}

Expand Down
31 changes: 31 additions & 0 deletions Tests/TurfTests/LineStringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,35 @@ class LineStringTests: XCTestCase {
sliced = line1.trimmed(from: startDistance, to: stopDistance)
XCTAssertNil(sliced, "should return nil")
}

func testIntersections() {
let lineString = LineString([.init(latitude: 2, longitude: 1),
.init(latitude: 2, longitude: 5),
.init(latitude: 2, longitude: 9)])

let intersectingLineString = LineString([.init(latitude: 4, longitude: 1),
.init(latitude: 0, longitude: 5),
.init(latitude: 2, longitude: 9)])

var intersections = lineString.intersections(with: intersectingLineString)

XCTAssertEqual(intersections.sorted(by: { $0.longitude < $1.longitude }),
[.init(latitude: 2, longitude: 3),
.init(latitude: 2, longitude: 9)],
accuracy: 0, "LineString intersections are not correct.")

let notIntersectingLineString = LineString([.init(latitude: 1, longitude: 1),
.init(latitude: 1, longitude: 5),
.init(latitude: 1, longitude: 9)])

intersections = lineString.intersections(with: notIntersectingLineString)

XCTAssertTrue(intersections.isEmpty, "Found impossible LineString intersection(s).")

let emptyLineString = LineString([])

intersections = lineString.intersections(with: emptyLineString)

XCTAssertTrue(intersections.isEmpty, "Found impossible LineString intersection(s).")
}
}
7 changes: 7 additions & 0 deletions Tests/TurfTests/TurfTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class TurfTests: XCTestCase {
XCTAssertEqual(a, coord1)
}

func testIntersectionOnEnd() {
let coord1 = LocationCoordinate2D(latitude: 20, longitude: 20)
let a = intersection((LocationCoordinate2D(latitude: 20, longitude: 20), LocationCoordinate2D(latitude: 40, longitude: 40)),
(LocationCoordinate2D(latitude: 20, longitude: 20), LocationCoordinate2D(latitude: 40, longitude: 20)))
XCTAssertEqual(a, coord1)
}

func testCLLocationDegrees() {
let degree: CLLocationDegrees = 100
let a = degree.toRadians()
Expand Down

0 comments on commit 43d144f

Please sign in to comment.