-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Only detect exact collinear intersections in traces #6980
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
Changes from 3 commits
2928232
5e39d4b
e6474c0
472058e
74a6464
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package org.odk.collect.shared.geometry.support | ||
|
|
||
| import org.odk.collect.shared.geometry.LineSegment | ||
| import org.odk.collect.shared.geometry.Point | ||
| import org.odk.collect.shared.geometry.Trace | ||
| import org.odk.collect.shared.geometry.segments | ||
| import kotlin.random.Random | ||
|
|
||
| object GeometryTestUtils { | ||
|
|
||
| fun getTraceGenerator(maxLength: Int = 10, maxCoordinate: Double = 100.0): Sequence<Trace> { | ||
| return generateSequence { | ||
| val length = Random.nextInt(3, maxLength) | ||
| val trace = Trace(0.until(length).map { | ||
| Point( | ||
| Random.nextDouble(maxCoordinate * -1, maxCoordinate), | ||
| Random.nextDouble(maxCoordinate * -1, maxCoordinate) | ||
| ) | ||
| }) | ||
|
|
||
| if (trace.isClosed()) { | ||
| trace | ||
| } else { | ||
| val shouldClose = Random.nextBoolean() | ||
| if (shouldClose) { | ||
| trace.copy(points = trace.points + trace.points.first()) | ||
| } else { | ||
| trace | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun Trace.reverse(): Trace { | ||
| return Trace(points.reversed()) | ||
| } | ||
|
|
||
| fun Trace.scale(factor: Double): Trace { | ||
| return Trace(points.map { | ||
| Point(it.x * factor, it.y * factor) | ||
| }) | ||
| } | ||
|
|
||
| fun Trace.addRandomIntersectingSegment(): Trace { | ||
| val intersectionSegment = segments().dropLast(1).random() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to ignore the last segment? As I understand it, it avoids the scenario when the additional segment lies on the last segment but why? This still would be treated as an intersection.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question! I'd meant to add a comment when moving this to a method and then forgotten: This is basically the "two consecutive segments intersect" special case, and we test this it specifically ( |
||
| val intersectPosition = Random.nextDouble(0.1, 1.0) | ||
| val intersectionPoint = intersectionSegment.interpolate(intersectPosition) | ||
| val lineSegment = LineSegment(points.last(), intersectionPoint) | ||
| val intersectingSegment = | ||
| LineSegment(lineSegment.start, lineSegment.interpolate(1.1)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a line that intersects through another (rather than just touching) avoids needing to use an epsilon to account for the inaccurate |
||
| return Trace(points + intersectingSegment.end) | ||
| } | ||
|
|
||
| /** | ||
| * Calculate a [Point] on this [LineSegment] based on the `position` using | ||
| * [Linear interpolation](https://en.wikipedia.org/wiki/Linear_interpolation). `0` will return | ||
| * [LineSegment.start] and `1` will return [LineSegment.end]. | ||
| */ | ||
| fun LineSegment.interpolate(position: Double): Point { | ||
| val x = start.x + position * (end.x - start.x) | ||
| val y = start.y + position * (end.y - start.y) | ||
| return Point(x, y) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package org.odk.collect.shared.geometry.support | ||
|
|
||
| import org.hamcrest.MatcherAssert.assertThat | ||
| import org.hamcrest.Matchers.equalTo | ||
| import org.junit.Test | ||
| import org.odk.collect.shared.geometry.LineSegment | ||
| import org.odk.collect.shared.geometry.Orientation | ||
| import org.odk.collect.shared.geometry.Point | ||
| import org.odk.collect.shared.geometry.orientation | ||
| import org.odk.collect.shared.geometry.support.GeometryTestUtils.interpolate | ||
| import org.odk.collect.shared.geometry.within | ||
|
|
||
| class GeometryTestUtilsTest { | ||
|
|
||
| @Test | ||
| fun `LineSegment#interpolate returns a point on the segment at a proportional distance`() { | ||
| val segment = LineSegment(Point(0.0, 0.0), Point(1.0, 0.0)) | ||
|
|
||
| assertThat(segment.interpolate(0.0), equalTo(Point(0.0, 0.0))) | ||
| assertThat(segment.interpolate(0.5), equalTo(Point(0.5, 0.0))) | ||
| assertThat(segment.interpolate(1.0), equalTo(Point(1.0, 0.0))) | ||
| } | ||
|
|
||
| @Test | ||
| fun `LineSegment#interpolate returns a collinear point within the line's bounding box`() { | ||
| val segment = LineSegment(Point(0.0, 0.0), Point(1.0, 1.0)) | ||
| val interpolatedPoint = segment.interpolate(0.5) | ||
|
|
||
| val orientation = orientation(interpolatedPoint, segment.start, segment.end) | ||
| assertThat(orientation, equalTo(Orientation.Collinear)) | ||
| assertThat(interpolatedPoint.within(segment), equalTo(true)) | ||
| } | ||
|
|
||
| @Test | ||
| fun `LineSegment#interpolate returns a collinear point within the line's bounding box for higher precision points with a suitable epsilon`() { | ||
| val segment = LineSegment(Point(56.6029153, 20.2311124), Point(56.6029192, 20.2310467)) | ||
| val interpolatedPoint = segment.interpolate(0.5) | ||
|
|
||
| val orientation = orientation(interpolatedPoint, segment.start, segment.end, epsilon = 0.000001) | ||
| assertThat(orientation, equalTo(Orientation.Collinear)) | ||
| assertThat(interpolatedPoint.within(segment), equalTo(true)) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the PR description, I'm assuming we're going to end up tuning an epsilon for the
intersectsXPath function or allowing it to be tweaked so I've left this in.