Skip to content

Only detect exact collinear intersections in traces#6980

Merged
grzesiek2010 merged 5 commits intogetodk:masterfrom
seadowg:epsilon
Dec 3, 2025
Merged

Only detect exact collinear intersections in traces#6980
grzesiek2010 merged 5 commits intogetodk:masterfrom
seadowg:epsilon

Conversation

@seadowg
Copy link
Member

@seadowg seadowg commented Dec 1, 2025

Closes #6979

Why is this the best possible solution? Were any other approaches considered?

I've removed the default epsilon from our orientation which is used as part of intersection detection. Instead, now an epsilon can be passed to both LineSegment#intersects and Trace#intersects. This allows us to only detect exact collinear intersections (one endpoint touching another line) in the app, while also allowing tolerance for testing with linear interpolation (which is hard to get 100% exact).

We may want to use an epsilon in the app as well, but until we get more feedback, avoiding false positives is safer - I think leaving the ability to add one in right now is a good idea.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

This should make it very hard to create an intersection where the endpoint of one segment is touching (or close to touching) another line.

Before submitting this PR, please make sure you have:

  • added or modified tests for any new or changed behavior
  • run ./gradlew connectedAndroidTest (or ./gradlew testLab) and confirmed all checks still pass
  • added a comment above any new strings describing it for translators
  • added any new strings with date formatting to DateFormatsTest
  • verified that any code or assets from external sources are properly credited in comments and/or in the about file.
  • verified that any new UI elements use theme colors. UI Components Style guidelines

@seadowg
Copy link
Member Author

seadowg commented Dec 1, 2025

@dbemke is this looking better?

@dbemke
Copy link

dbemke commented Dec 2, 2025

is this looking better?

Issue #6979 and the one described in #6967 don't show that they are invalid polygon.
I tried to "create" similar one and none of them violated the constraint.
I manually moved the points so that when I zoom in I can see them in one line and they don't violate the constraint - I'm not sure whether this case should be treated as intersection or not. @alyblenkin What do you think? Is it the result users want (the sides of the polygon are super close and are treated as a valid polygon) or his is the case they want to avoid?
Steps to reproduce:

  1. Scan user "invalid polygons" https://test.getodk.cloud/projects/629/app-users
  2. Go to geoshape and geotrace defaults example to "test same line max" and open the map.

The geometry is also in https://docs.google.com/document/d/1daqyvOECyh6pYMeealpVbPWrmONF4DgUHVvrcbRgWrE/edit?tab=t.0

@seadowg
Copy link
Member Author

seadowg commented Dec 2, 2025

Issue #6979 and the one described in #6967 don't show that they are invalid polygon.
I tried to "create" similar one and none of them violated the constraint.
I manually moved the points so that when I zoom in I can see them in one line and they don't violate the constraint - I'm not sure whether this case should be treated as intersection or not.

Good to discuss further, but for this PR let's go with it as-is. I think we're going to need some feedback from users: making endpoint intersections "fuzzy" could be disastrous for some cases.

@seadowg seadowg marked this pull request as ready for review December 2, 2025 10:18
@seadowg seadowg requested a review from grzesiek2010 December 2, 2025 10:18
val intersectionPoint = intersectionSegment.interpolate(intersectPosition)
val lineSegment = LineSegment(points.last(), intersectionPoint)
val intersectingSegment =
LineSegment(lineSegment.start, lineSegment.interpolate(1.1))
Copy link
Member Author

Choose a reason for hiding this comment

The 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 interpolate and is far more realistic.

* Returns `true` if any segment of the trace intersects with any other and `false` otherwise.
*/
fun Trace.intersects(): Boolean {
fun Trace.intersects(epsilon: Double = 0.0): Boolean {
Copy link
Member Author

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 intersects XPath function or allowing it to be tweaked so I've left this in.

@seadowg seadowg requested a review from grzesiek2010 December 2, 2025 11:37
}

fun Trace.addRandomIntersectingSegment(): Trace {
val intersectionSegment = segments().dropLast(1).random()
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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:

/**
* Choose random segment, a random (interpolated) point on that segment and then create a new
* trace with an additional point just beyond that to create an intersecting trace.
*
* Never chooses the last segment as a target for intersecting as that can only create a
* collinear intersection which is unlikely to be accurate due to inaccuracies in [interpolate].
*/

This is basically the "two consecutive segments intersect" special case, and we test this it specifically (Trace#intersects returns true when two segments and they intersect).

@seadowg seadowg requested a review from grzesiek2010 December 2, 2025 11:53
@WKobus
Copy link

WKobus commented Dec 3, 2025

Tested with success

Verified on Android 16, 11

Verified cases:

@dbemke
Copy link

dbemke commented Dec 3, 2025

Tested with success

Verified on Android 10

@grzesiek2010 grzesiek2010 merged commit f725a3b into getodk:master Dec 3, 2025
7 checks passed
@seadowg seadowg deleted the epsilon branch December 3, 2025 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid geoshape with points which are almost in the same line

4 participants