diff --git a/README.md b/README.md index ac08df76..ec18b569 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Turf.js | Turf for Swift [turf-helpers#radiansToDegrees](https://turfjs.org/docs/#radiansToDegrees) | `CLLocationDegrees.toDegrees()`
`LocationDegrees.toDegrees()` on Linux [turf-helpers#convertLength](https://turfjs.org/docs/#convertLength)
[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(_:_:)` diff --git a/Sources/Turf/Geometries/LineString.swift b/Sources/Turf/Geometries/LineString.swift index 09afb02b..614c2c33 100644 --- a/Sources/Turf/Geometries/LineString.swift +++ b/Sources/Turf/Geometries/LineString.swift @@ -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 { @@ -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 { @@ -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() + 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 + } + } } diff --git a/Sources/Turf/Turf.swift b/Sources/Turf/Turf.swift index 8129d7a1..10e79892 100644 --- a/Sources/Turf/Turf.swift +++ b/Sources/Turf/Turf.swift @@ -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/ @@ -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 } diff --git a/Tests/TurfTests/LineStringTests.swift b/Tests/TurfTests/LineStringTests.swift index 8446925b..c0d047c1 100644 --- a/Tests/TurfTests/LineStringTests.swift +++ b/Tests/TurfTests/LineStringTests.swift @@ -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).") + } } diff --git a/Tests/TurfTests/TurfTests.swift b/Tests/TurfTests/TurfTests.swift index 8e3681a0..5344870d 100644 --- a/Tests/TurfTests/TurfTests.swift +++ b/Tests/TurfTests/TurfTests.swift @@ -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()