Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1c5868b
Add intersects util
seadowg Nov 10, 2025
fd4a6a1
Add intersects function handler
seadowg Nov 11, 2025
ab235b8
Use prototypes to get already evaluated args
seadowg Nov 11, 2025
4fb7739
Add test for non geotrace/shape strings
seadowg Nov 17, 2025
3419811
Add test cases for repeated segments and closed traced intersection
seadowg Nov 17, 2025
9641e08
Add intersects function handler to Collect
seadowg Nov 17, 2025
b2f3cc7
Move parseGeometry and parseGeometryPoint tests to geo
seadowg Nov 17, 2025
a11a20d
Convert GeoUtils to Kotlin
seadowg Nov 17, 2025
7508fe8
Move parseGeometryPoint to GeoUtils
seadowg Nov 17, 2025
17e52e8
Fix import in test
seadowg Nov 18, 2025
83a60e2
Throw exception for non-geotrace inputs
seadowg Nov 18, 2025
281fd94
Improve converted code
seadowg Nov 18, 2025
d21470c
Use zipWithNext to simplify creating segments
seadowg Nov 18, 2025
7256252
Add test to check arg length is enforced
seadowg Nov 18, 2025
77bec29
Switch test to Hamcrest
seadowg Nov 18, 2025
2a3cd02
Remove unused imports
seadowg Nov 19, 2025
da8e9a7
Add another test case to make sure we check intersections from both s…
seadowg Nov 19, 2025
d853d05
Add failing tests for non crossing intersections
seadowg Nov 20, 2025
292b7be
Rename method
seadowg Nov 20, 2025
e074729
Fix origin of one segment touching another case
seadowg Nov 20, 2025
7055bdc
Correct test
seadowg Nov 20, 2025
527ae8e
Fix line moving back on itself case
seadowg Nov 20, 2025
e7a8662
Add test for shape that closes outside the origin
seadowg Nov 20, 2025
0800a0e
Simplify non-origin vertex closing
seadowg Nov 21, 2025
602fee7
Use bounding box check based on orientation to solve right angled tri…
seadowg Nov 21, 2025
ad81da9
Fix special case with two segmnt intersection
seadowg Nov 21, 2025
69d4c8a
Pull out specific 2D geometry code
seadowg Nov 21, 2025
8860a4a
Make sure checking instersection between segments is exhaustive
seadowg Nov 21, 2025
e7b2a71
Remove unneeded return
seadowg Nov 25, 2025
30dbba2
Correct bounding box check direction
seadowg Nov 25, 2025
8822207
Add additional (failing) test for 2 segment self intersection
seadowg Nov 26, 2025
8e0e0c7
Account for case where first endpoint intersects with second segment …
seadowg Nov 26, 2025
970b508
Filter out zero length segments in traces
seadowg Nov 26, 2025
1d1a320
Remove unneeded return
seadowg Nov 26, 2025
bba4264
Fix reversed line with 3 points case
seadowg Nov 27, 2025
2fb19f8
Add basic metamorphic test for intersects
seadowg Nov 27, 2025
9f596bc
Improve adding intersecting segment using interpolation
seadowg Nov 27, 2025
29936a8
Add epsilon to colinearity check to prevent precision errors
seadowg Nov 27, 2025
08c5997
Fix accidental map
seadowg Nov 27, 2025
c6aa7c0
Add docs for interpolate
seadowg Nov 27, 2025
6a0e2e0
Increase the number of possible random intersection points in test
seadowg Nov 28, 2025
ac13533
Add tests for LineSegment#interpolate
seadowg Nov 28, 2025
d88de71
Add quickCheck helper
seadowg Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ ktlint_standard_condition-wrapping = disabled
ktlint_standard_function-literal = disabled
ktlint_standard_backing-property-naming = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_no-unused-imports=enabled
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.odk.collect.entities.javarosa.filter.PullDataFunctionHandler
import org.odk.collect.entities.javarosa.finalization.EntityFormFinalizationProcessor
import org.odk.collect.entities.storage.EntitiesRepository
import org.odk.collect.forms.instances.Instance
import org.odk.collect.geo.javarosa.IntersectsFunctionHandler
import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.shared.settings.Settings
import java.io.File
Expand All @@ -46,6 +47,9 @@ class CollectFormEntryControllerFactory(
externalDataHandlerPull
)
)

it.addFunctionHandler(IntersectsFunctionHandler())
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm in two minds about this being untested. It can be seen as pure "configuration" from one angle, but also if I delete it then intersects won't work. Maybe it's worth replacing one of the IntersectsFunctionHandler tests (probably the detects intersection path) with a feature test?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's worth replacing one of the IntersectsFunctionHandler tests (probably the detects intersection path) with a feature test?

I think it would make sense.


it.addPostProcessor(EntityFormFinalizationProcessor())
it.addPostProcessor(EditedFormFinalizationProcessor(instance))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package org.odk.collect.android.widgets;

import static org.odk.collect.geo.GeoUtils.parseGeometryPoint;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
Expand Down Expand Up @@ -83,7 +85,7 @@ protected View onCreateWidgetView(Context context, FormEntryPrompt prompt, int a

@Override
public IAnswerData getAnswer() {
double[] parsedGeometryPoint = GeoWidgetUtils.parseGeometryPoint(answerText);
double[] parsedGeometryPoint = parseGeometryPoint(answerText);
return parsedGeometryPoint == null
? null
: new GeoPointData(parsedGeometryPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package org.odk.collect.android.widgets;

import static org.odk.collect.geo.GeoUtils.parseGeometryPoint;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
Expand Down Expand Up @@ -76,7 +78,7 @@ protected View onCreateWidgetView(Context context, FormEntryPrompt prompt, int a

@Override
public IAnswerData getAnswer() {
double[] parsedGeometryPoint = GeoWidgetUtils.parseGeometryPoint(answerText);
double[] parsedGeometryPoint = parseGeometryPoint(answerText);
return parsedGeometryPoint == null
? null
: new GeoPointData(parsedGeometryPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.odk.collect.androidshared.livedata.NonNullLiveData
import org.odk.collect.androidshared.ui.FragmentFactoryBuilder
import org.odk.collect.async.Scheduler
import org.odk.collect.entities.javarosa.parse.EntitySchema
import org.odk.collect.geo.geopoly.GeoPolyUtils.parseGeometry
import org.odk.collect.geo.selection.IconifiedText
import org.odk.collect.geo.selection.MappableSelectItem
import org.odk.collect.geo.selection.SelectionMapData
Expand Down Expand Up @@ -139,7 +140,7 @@ internal class SelectChoicesMapData(

if (geometry != null) {
try {
val points = GeoWidgetUtils.parseGeometry(geometry)
val points = parseGeometry(geometry)
if (points.isNotEmpty()) {
val withinBounds = points.all {
GeoWidgetUtils.isWithinMapBounds(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.odk.collect.geo.Constants.EXTRA_RETAIN_MOCK_ACCURACY
import org.odk.collect.geo.geopoint.GeoPointActivity
import org.odk.collect.geo.geopoint.GeoPointMapActivity
import org.odk.collect.geo.geopoly.GeoPolyActivity
import org.odk.collect.geo.geopoly.GeoPolyUtils.parseGeometry
import org.odk.collect.permissions.PermissionListener
import org.odk.collect.permissions.PermissionsProvider
import java.lang.Boolean.parseBoolean
Expand All @@ -35,7 +36,7 @@ class ActivityGeoDataRequester(
waitingForDataRegistry.waitForData(prompt.index)

val bundle = Bundle().also {
val parsedGeometry = GeoWidgetUtils.parseGeometry(answerText)
val parsedGeometry = parseGeometry(answerText)
if (parsedGeometry.isNotEmpty()) {
it.putParcelable(
GeoPointMapActivity.EXTRA_LOCATION,
Expand Down Expand Up @@ -97,7 +98,7 @@ class ActivityGeoDataRequester(
val intent = Intent(activity, GeoPolyActivity::class.java).also {
it.putExtra(
GeoPolyActivity.EXTRA_POLYGON,
GeoWidgetUtils.parseGeometry(answerText)
ArrayList(parseGeometry(answerText))
)
it.putExtra(
GeoPolyActivity.OUTPUT_MODE_KEY,
Expand Down Expand Up @@ -130,7 +131,7 @@ class ActivityGeoDataRequester(
val intent = Intent(activity, GeoPolyActivity::class.java).also {
it.putExtra(
GeoPolyActivity.EXTRA_POLYGON,
GeoWidgetUtils.parseGeometry(answerText)
ArrayList(parseGeometry(answerText))
)
it.putExtra(
GeoPolyActivity.OUTPUT_MODE_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,40 +41,6 @@ object GeoWidgetUtils {
}
}

@JvmStatic
fun parseGeometryPoint(answer: String?): DoubleArray? {
if (answer != null && answer.isNotEmpty()) {
val sa = answer.trim { it <= ' ' }.split(" ").toTypedArray()
return try {
doubleArrayOf(
sa[0].toDouble(),
if (sa.size > 1) sa[1].toDouble() else 0.0,
if (sa.size > 2) sa[2].toDouble() else 0.0,
if (sa.size > 3) sa[3].toDouble() else 0.0
)
} catch (e: Throwable) {
null
}
} else {
return null
}
}

fun parseGeometry(geometry: String?): ArrayList<MapPoint> {
val points = ArrayList<MapPoint>()

for (vertex in (geometry ?: "").split(";").toTypedArray()) {
val point = parseGeometryPoint(vertex)
if (point != null) {
points.add(MapPoint(point[0], point[1], point[2], point[3]))
} else {
return ArrayList()
}
}

return points
}

fun isWithinMapBounds(point: MapPoint): Boolean {
return point.latitude.absoluteValue <= 90 && point.longitude.absoluteValue <= 180
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.promptWithReadOnlyAndAnswer;
import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetDependencies;
import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetTestActivity;
import static org.odk.collect.geo.GeoUtils.parseGeometryPoint;

@RunWith(AndroidJUnit4.class)
public class GeoPointMapWidgetTest {
Expand All @@ -51,7 +52,7 @@ public void getAnswer_whenPromptDoesNotHaveAnswer_returnsNull() {
public void getAnswer_whenPromptHasAnswer_returnsPromptAnswer() {
GeoPointMapWidget widget = createWidget(promptWithAnswer(answer));
assertEquals(widget.getAnswer().getDisplayText(),
new GeoPointData(GeoWidgetUtils.parseGeometryPoint(answer.getDisplayText())).getDisplayText());
new GeoPointData(parseGeometryPoint(answer.getDisplayText())).getDisplayText());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ import org.hamcrest.Matchers.equalTo
import org.javarosa.core.model.data.GeoPointData
import org.junit.Test
import org.junit.runner.RunWith
import org.odk.collect.android.R
import org.odk.collect.android.widgets.support.GeoWidgetHelpers
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.convertCoordinatesIntoDegreeFormat
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.floor
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.getGeoPointAnswerToDisplay
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.getGeoPolyAnswerToDisplay
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.isWithinMapBounds
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.parseGeometry
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.parseGeometryPoint
import org.odk.collect.android.widgets.utilities.GeoWidgetUtils.truncateDouble
import org.odk.collect.maps.MapPoint

Expand Down Expand Up @@ -102,32 +99,6 @@ class GeoWidgetUtilsTest {
assertEquals("qwerty", floor("qwerty"))
}

@Test
fun parseGeometryPointTest() {
var gp =
parseGeometryPoint("37.45153333333334 -122.15539166666667 0.0 20.0")!!
assertEquals(37.45153333333334, gp[0])
assertEquals(-122.15539166666667, gp[1])
assertEquals(0.0, gp[2])
assertEquals(20.0, gp[3])

gp = parseGeometryPoint("37.45153333333334")!!
assertEquals(37.45153333333334, gp[0])
assertEquals(0.0, gp[1])
assertEquals(0.0, gp[2])
assertEquals(0.0, gp[3])

gp = parseGeometryPoint(" 37.45153333333334 -122.15539166666667 0.0 ")!!
assertEquals(37.45153333333334, gp[0])
assertEquals(-122.15539166666667, gp[1])
assertEquals(0.0, gp[2])
assertEquals(0.0, gp[3])

assertEquals(null, parseGeometryPoint("37.45153333333334 -122.15539166666667 0.0 qwerty"))
assertEquals(null, parseGeometryPoint(""))
assertEquals(null, parseGeometryPoint(null))
}

@Test
fun truncateDoubleTest() {
assertEquals("5", truncateDouble("5"))
Expand All @@ -141,22 +112,6 @@ class GeoWidgetUtilsTest {
assertEquals("", truncateDouble("qwerty"))
}

@Test
fun parseGeometryTest() {
assertThat(parseGeometry("1.0 2.0 3 4"), equalTo(listOf(MapPoint(1.0, 2.0, 3.0, 4.0))))
assertThat(
parseGeometry("1.0 2.0 3 4; 5.0 6.0 7 8"),
equalTo(listOf(MapPoint(1.0, 2.0, 3.0, 4.0), MapPoint(5.0, 6.0, 7.0, 8.0)))
)

assertThat(parseGeometry("blah"), equalTo(emptyList()))
assertThat(parseGeometry("1.0 2.0 3 4; blah"), equalTo(emptyList()))
assertThat(
parseGeometry("37.45153333333334 -122.15539166666667 0.0 qwerty"),
equalTo(emptyList())
)
}

@Test
fun isWithinMapBoundsTest() {
assertThat(isWithinMapBounds(MapPoint(90.0, 0.0, 0.0, 0.0)), equalTo(true))
Expand Down
Loading