Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,6 @@ internal constructor(
.stateIn(viewModelScope, WhileSubscribed(5_000), emptyList())
}



override fun initialize(
job: Job,
task: Task,
Expand Down Expand Up @@ -326,36 +324,37 @@ internal constructor(
* This coroutine runs on [viewModelScope] to ensure lifecycle safety.
*/
private fun refreshMap() = viewModelScope.launch {
if (session.vertices.isEmpty()) {
val vertices = session.displayVertices
if (vertices.isEmpty()) {
_draftArea.emit(null)
draftTag = null
} else {
if (draftTag == null) {
val feature = buildPolygonFeature()
val feature = buildPolygonFeature(vertices)
draftTag = feature.tag
_draftArea.emit(feature)
} else {
val feature = buildPolygonFeature(id = draftTag!!.id)
val feature = buildPolygonFeature(vertices, id = draftTag!!.id)
_draftUpdates.tryEmit(feature)
}
}
}

private suspend fun buildPolygonFeature(id: String? = null) =
private suspend fun buildPolygonFeature(vertices: List<Coordinates>, id: String? = null) =
Feature(
id = id ?: uuidGenerator.generateUuid(),
type = Feature.Type.USER_POLYGON,
geometry = LineString(session.vertices),
geometry = LineString(vertices),
style = featureStyle,
clusterable = false,
selected = true,
tooltipText = getDistanceTooltipText(),
tooltipText = getDistanceTooltipText(vertices),
)

/** Returns the distance in meters between the last two vertices for displaying in the tooltip. */
private fun getDistanceTooltipText(): String? {
if (sessionState.value.isMarkedComplete || session.vertices.size <= 1) return null
val distance = session.vertices.penult().distanceTo(session.vertices.last())
private fun getDistanceTooltipText(vertices: List<Coordinates>): String? {
if (sessionState.value.isMarkedComplete || vertices.size <= 1) return null
val distance = vertices.penult().distanceTo(vertices.last())
if (distance < TOOLTIP_MIN_DISTANCE_METERS) return null
return localeAwareMeasureFormatter.formatDistance(distance, measurementUnits)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ interface PolygonDrawingSession {
/** The list of committed vertices in the current drawing session. */
val vertices: List<Coordinates>

/** The complete list of vertices (committed + tentative) to display on the map. */
val displayVertices: List<Coordinates>

/** Whether the current polygon geometry self-intersects. */
val hasSelfIntersection: Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import kotlinx.collections.immutable.toImmutableList
import org.groundplatform.android.ui.datacollection.tasks.polygon.PolygonDrawingSession.Companion.DISTANCE_THRESHOLD_DP
import org.groundplatform.domain.model.geometry.Coordinates
import org.groundplatform.domain.model.geometry.LineString
import org.groundplatform.domain.util.isClosed
import org.groundplatform.domain.util.isSelfIntersecting

class PolygonDrawingSessionImpl : PolygonDrawingSession {
Expand All @@ -34,56 +35,57 @@ class PolygonDrawingSessionImpl : PolygonDrawingSession {
override val vertices: List<Coordinates>
get() = _vertices

private var _tentativeVertex: Coordinates? = null

override val displayVertices: List<Coordinates>
get() = _tentativeVertex?.let { _vertices + it } ?: _vertices

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val redoVertexStack = mutableListOf<Coordinates>()

override val hasSelfIntersection: Boolean
get() = isSelfIntersecting(_vertices)

private fun addVertex(vertex: Coordinates, shouldOverwriteLastVertex: Boolean) {
val updatedVertices = _vertices.toMutableList()

if (shouldOverwriteLastVertex && updatedVertices.isNotEmpty()) {
updatedVertices.removeAt(updatedVertices.lastIndex)
}

updatedVertices.add(vertex)

_vertices = updatedVertices.toImmutableList()
}
get() = isSelfIntersecting(displayVertices)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I can't seem to close the polygons. When debugging i noticed that isSelfIntersecting now returns true when getting the state of the complete button. It could be because now this method checks displayVertices which includes the tentativeVertex, and that is equal to the first vertex

Screen_recording_20260521_143226.webm

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm unable to reproduce this on my device. Are you able to consistently reproduce it or is it flaky?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it's consistently reproducible. But I've found out it's because I was clicking twice to drop the first point because clicking the first time doesn't make the white circle marker to show up until the camera is dragged. So I believe the problem is that dropping two points in the starting vertex is allowed, but then leads to an invalid polygon


override fun setVertices(newVertices: List<Coordinates>) {
_vertices = newVertices.toImmutableList()
_tentativeVertex = null
redoVertexStack.clear()
}

override fun updateTentativeVertex(
target: Coordinates,
calculateDistance: (Coordinates, Coordinates) -> Double,
) {
if (isClosed(_vertices)) {
_tentativeVertex = null
_isTooClose = true
return
}

val firstVertex = _vertices.firstOrNull()
var updatedTarget = target
if (firstVertex != null && _vertices.size > 2) {
if (firstVertex != null && _vertices.size >= 3) {
val distance = calculateDistance(firstVertex, target)

if (distance <= DISTANCE_THRESHOLD_DP) {
updatedTarget = firstVertex
}
}

val prev = _vertices.dropLast(1).lastOrNull()
val lastCommitted = _vertices.lastOrNull()
_isTooClose =
_vertices.size > 1 &&
prev?.let { calculateDistance(it, target) <= DISTANCE_THRESHOLD_DP } == true
lastCommitted?.let { calculateDistance(it, target) <= DISTANCE_THRESHOLD_DP } == true

addVertex(updatedTarget, true)
_tentativeVertex = updatedTarget
}

override fun commitTentativeVertex(currentCameraTarget: Coordinates?): List<Coordinates>? {
redoVertexStack.clear()
val vertex = _vertices.lastOrNull() ?: currentCameraTarget
val vertex = _tentativeVertex ?: currentCameraTarget
return vertex?.let {
_vertices = (_vertices + it).toImmutableList()
_tentativeVertex = null
_isTooClose = _vertices.size > 1
addVertex(it, false)
_vertices
}
}
Expand All @@ -97,20 +99,30 @@ class PolygonDrawingSessionImpl : PolygonDrawingSession {
}

override fun isValidPolygon(): Boolean =
LineString(_vertices).isClosed() && !isSelfIntersecting(_vertices)
LineString(displayVertices).isClosed() &&
!isSelfIntersecting(displayVertices) &&
displayVertices.distinct().size >= 3

override fun complete() {
check(isValidPolygon()) { "Polygon is not valid" }
check(!_isMarkedComplete) { "Already marked complete" }
if (_tentativeVertex != null) {
commitTentativeVertex(null)
}
_isMarkedComplete = true
}

override fun removeLastVertex(): Boolean {
if (_vertices.isEmpty()) return false
if (_tentativeVertex == null && _vertices.isEmpty()) return false

_isMarkedComplete = false
redoVertexStack.add(_vertices.last())
_vertices = _vertices.dropLast(1).toImmutableList()
_tentativeVertex = null

if (_vertices.isNotEmpty()) {
redoVertexStack.add(_vertices.last())
_vertices = _vertices.dropLast(1).toImmutableList()
}

return true
}

Expand All @@ -120,6 +132,7 @@ class PolygonDrawingSessionImpl : PolygonDrawingSession {
_isMarkedComplete = false
val redoVertex = redoVertexStack.removeAt(redoVertexStack.lastIndex)
_vertices = (_vertices + redoVertex).toImmutableList()
_tentativeVertex = null
return redoVertex
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ class DrawAreaTaskScreenTest : BaseHiltTest() {
Coordinates(0.0, 0.0),
Coordinates(10.0, 10.0),
Coordinates(20.0, 20.0),
Coordinates(20.0, 20.0),
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() {

viewModel.removeLastVertex()

assertGeometry(3, isLineString = true)
assertGeometry(2, isLineString = true)
}

@Test
Expand Down Expand Up @@ -199,11 +199,11 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() {

viewModel.removeLastVertex()

assertThat(featureTestObserver.value()?.tooltipText).isEqualTo("1,565,109 m")
assertThat(featureTestObserver.value()?.tooltipText).isNull()
Comment thread
shobhitagarwal1612 marked this conversation as resolved.

viewModel.removeLastVertex()

assertThat(featureTestObserver.value()?.tooltipText).isEqualTo(null)
assertThat(featureTestObserver.value()?.tooltipText).isNull()
}

@Test
Expand Down Expand Up @@ -320,14 +320,34 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() {
assertThat(firstLine.coordinates.size).isEqualTo(1)
assertThat(firstFeature.tooltipText).isNull()

updateLastVertexAndAdd(COORDINATE_2)
advanceUntilIdle()
val secondFeature = viewModel.draftArea.first()
val secondLine = secondFeature!!.geometry as LineString
assertThat(secondLine.coordinates.size).isEqualTo(1)
assertThat(secondFeature.tooltipText).isNull()
viewModel.draftUpdates.test {
updateLastVertexAndAdd(COORDINATE_2)
advanceUntilIdle()

viewModel.removeLastVertex()
// 1. Consume the transient active target update (from updateLastVertex)
val secondFeature = awaitItem()
val secondLine = secondFeature.geometry as LineString
assertThat(secondLine.coordinates.size).isEqualTo(2)
assertThat(secondFeature.tooltipText).isEqualTo("1,565,109 m")

// 2. Consume the committed vertex update (from addLastVertex)
val committedFeature = awaitItem()
val committedLine = committedFeature.geometry as LineString
assertThat(committedLine.coordinates.size).isEqualTo(2)
assertThat(committedFeature.tooltipText).isEqualTo("1,565,109 m")

// 3. Revert/Undo last vertex
viewModel.removeLastVertex()
advanceUntilIdle()

// 4. Consume the undo update (from removeLastVertex)
val thirdFeature = awaitItem()
val thirdLine = thirdFeature.geometry as LineString
assertThat(thirdLine.coordinates.size).isEqualTo(1)
assertThat(thirdFeature.tooltipText).isNull()

cancelAndIgnoreRemainingEvents()
}
}

@Test
Expand Down Expand Up @@ -568,10 +588,10 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() {
updateLastVertexAndAdd(COORDINATE_2)

viewModel.removeLastVertex()
assertGeometry(2, isLineString = true)
assertGeometry(1, isLineString = true)

viewModel.redoLastVertex()
assertGeometry(3, isLineString = true)
assertGeometry(2, isLineString = true)
}

@Test
Expand Down
Loading