Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linestring.intersection method #167

Merged
merged 4 commits into from
Nov 10, 2021
Merged

Linestring.intersection method #167

merged 4 commits into from
Nov 10, 2021

Conversation

Udumft
Copy link
Contributor

@Udumft Udumft commented Oct 29, 2021

Resolves #141

PR adds Linestring.intersection(with:) method.

@Udumft Udumft added the improvement Improvement for an existing feature. label Oct 29, 2021
@Udumft Udumft added this to the v2.1-beta milestone Oct 29, 2021
@Udumft Udumft requested a review from a team October 29, 2021 12:36
@Udumft Udumft self-assigned this Oct 29, 2021
Comment on lines 319 to 320
zip(coordinates.dropLast(), coordinates.dropFirst()).forEach { segment1 in
zip(line.coordinates.dropLast(), line.coordinates.dropFirst()).forEach { segment2 in
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this method complexity could be reduced. Original implementation has tree search for corresponding segments, but this one just relies on denominator check in intersection(_:, _:).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original implementation has tree search for corresponding segments

Turf.js’s use of an optimized R-tree from RBush is key to the method’s high performance. There is a similar GKRTree in GameplayKit, and apparently some support in Core Data as well, but I’m not sure we’d want to maintain a separate codepath for non-Apple platforms. I also haven’t found any good RTree implementations in pure Swift yet; this package stores the tree inefficiently on disk. Let’s ticket out a port of RBush separately as a performance optimization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket to track porting.

Sources/Turf/Geometries/LineString.swift Outdated Show resolved Hide resolved
Comment on lines 319 to 320
zip(coordinates.dropLast(), coordinates.dropFirst()).forEach { segment1 in
zip(line.coordinates.dropLast(), line.coordinates.dropFirst()).forEach { segment2 in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original implementation has tree search for corresponding segments

Turf.js’s use of an optimized R-tree from RBush is key to the method’s high performance. There is a similar GKRTree in GameplayKit, and apparently some support in Core Data as well, but I’m not sure we’d want to maintain a separate codepath for non-Apple platforms. I also haven’t found any good RTree implementations in pure Swift yet; this package stores the tree inefficiently on disk. Let’s ticket out a port of RBush separately as a performance optimization.

zip(line.coordinates.dropLast(), line.coordinates.dropFirst()).forEach { segment2 in
if let intersection = Turf.intersection(LineSegment(segment1.0, segment1.1),
LineSegment(segment2.0, segment2.1)) {
let key = "\(intersection.latitude),\(intersection.longitude)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A string representation is kind of weird, but it’s probably the most straightforward approach until swiftlang/swift#37398 lands, since we wouldn’t be able to have LocationCoordinate2D privately conform to Hashable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about floating-point accuracy here. And about time complexity constant.
What if we implement a small struct like

private struct HashableCoordinate: Hashable {
    var latitude: Double
    var longitude: Double
}

And use Set<HashableCoordinate> that is then converted to an array of coordinates.
Or is it too much? May it be useful somewhere else in Turf?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would work. Pedantically, Turf should represent GeoJSON Point objects as its own hashable Point type instead of reusing CLLocationCoordinate2D. We stuck to the latter type as a practical matter, to avoid excessive conversions to and from a very common type. This intersection method’s performance would be dominated by the non-RTree-based intersection search, but you have a point that the struct representation would give us more certainty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented a Hashable wrapper

Sources/Turf/Geometries/LineString.swift Outdated Show resolved Hide resolved
Sources/Turf/Geometries/LineString.swift Outdated Show resolved Hide resolved
Sources/Turf/Geometries/LineString.swift Outdated Show resolved Hide resolved
@Udumft Udumft mentioned this pull request Nov 8, 2021
zip(line.coordinates.dropLast(), line.coordinates.dropFirst()).forEach { segment2 in
if let intersection = Turf.intersection(LineSegment(segment1.0, segment1.1),
LineSegment(segment2.0, segment2.1)) {
let key = "\(intersection.latitude),\(intersection.longitude)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about floating-point accuracy here. And about time complexity constant.
What if we implement a small struct like

private struct HashableCoordinate: Hashable {
    var latitude: Double
    var longitude: Double
}

And use Set<HashableCoordinate> that is then converted to an array of coordinates.
Or is it too much? May it be useful somewhere else in Turf?

@@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this was intended or not, but strict comparison here will not detect an intersection by line's ending points (for example when 2 LineStrings share the same coordinate). turf-line-intersect has such detection, but Turfjs's point-on-line implementation (which seems to be used as base reference for Swift port) for some reason does not. It seems like a bug to me, as when I seek for intersections between lines I usually want to include end points, especially if it can happen even with LineString's intermediate points, so I decided to fix that aswell.

.init(latitude: 0, longitude: 5),
.init(latitude: 2, longitude: 9)])

let intersections = lineString.intersections(with: intersectingLineString)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we test edge cases? Like "empty LineString" and "no intersections"?

@Udumft Udumft force-pushed the vk/141-linestring-intersection branch from 0f4b821 to 16049b2 Compare November 10, 2021 09:16
@bamx23
Copy link
Contributor

bamx23 commented Nov 10, 2021

LGTM

@Udumft Udumft merged commit 43d144f into main Nov 10, 2021
@bamx23 bamx23 mentioned this pull request Nov 11, 2021
nighthawk added a commit to maparoni/GeoJSONKit-Turf that referenced this pull request Jun 1, 2022
nighthawk added a commit to maparoni/GeoJSONKit-Turf that referenced this pull request Jun 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improvement Improvement for an existing feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add LineString intersection method
3 participants