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

Add move node action #4579

Merged
merged 14 commits into from
Dec 1, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.westnordost.streetcomplete.data.edithistory
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.edits.ElementEdit
import de.westnordost.streetcomplete.data.osm.edits.delete.DeletePoiNodeAction
import de.westnordost.streetcomplete.data.osm.edits.move.MoveNodeAction
import de.westnordost.streetcomplete.data.osm.edits.split_way.SplitWayAction
import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestHidden
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEdit
Expand All @@ -28,6 +29,7 @@ val Edit.overlayIcon: Int get() = when (this) {
when (action) {
is DeletePoiNodeAction -> R.drawable.ic_undo_delete
is SplitWayAction -> R.drawable.ic_undo_split
is MoveNodeAction -> R.drawable.ic_undo_move_node
else -> 0
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import de.westnordost.streetcomplete.data.osm.edits.create.CreateNodeAction
import de.westnordost.streetcomplete.data.osm.edits.create.RevertCreateNodeAction
import de.westnordost.streetcomplete.data.osm.edits.delete.DeletePoiNodeAction
import de.westnordost.streetcomplete.data.osm.edits.delete.RevertDeletePoiNodeAction
import de.westnordost.streetcomplete.data.osm.edits.move.MoveNodeAction
import de.westnordost.streetcomplete.data.osm.edits.move.RevertMoveNodeAction
import de.westnordost.streetcomplete.data.osm.edits.split_way.SplitWayAction
import de.westnordost.streetcomplete.data.osm.edits.update_tags.RevertUpdateElementTagsAction
import de.westnordost.streetcomplete.data.osm.edits.update_tags.UpdateElementTagsAction
Expand Down Expand Up @@ -48,6 +50,8 @@ class ElementEditsDao(
subclass(RevertDeletePoiNodeAction::class)
subclass(CreateNodeAction::class)
subclass(RevertCreateNodeAction::class)
subclass(MoveNodeAction::class)
subclass(RevertMoveNodeAction::class)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.westnordost.streetcomplete.data.osm.edits

import de.westnordost.streetcomplete.data.osm.edits.move.MoveNodeAction
import de.westnordost.streetcomplete.data.osm.edits.move.RevertMoveNodeAction
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryCreator
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry
Expand Down Expand Up @@ -81,7 +83,7 @@ class MapDataWithEditsSource internal constructor(

rebuildLocalChanges()

/* nothingChanged can be false at this point when e.g. there are two edits on the
/* nothingChanged can be false at this point when e.g. there are two edits on the
same element, and onUpdated is called after the first edit is uploaded. */
val nothingChanged = deletedIsUnchanged && elementsThatMightHaveChangedByKey.all {
val updatedElement = get(it.first.type, it.first.id)
Expand Down Expand Up @@ -184,6 +186,15 @@ class MapDataWithEditsSource internal constructor(
// element that got edited by the deleted edit not found? Hmm, okay then (not sure if this can happen at all)
elementsToDelete.add(ElementKey(edit.elementType, edit.elementId))
}

if (edit.action is MoveNodeAction || edit.action is RevertMoveNodeAction) {
val waysContainingNode = getWaysForNode(edit.elementId)
val affectedRelations = getRelationsForNode(edit.elementId) + waysContainingNode.flatMap { getRelationsForWay(it.id) }
for (elem in waysContainingNode + affectedRelations) {
mapData.put(elem, getGeometry(elem.type, elem.id))
}
}

westnordost marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -427,7 +438,21 @@ class MapDataWithEditsSource internal constructor(
updatedElements[key] = element
updatedGeometries[key] = createGeometry(element)
}
return MapDataUpdates(updated = updates, deleted = deletedKeys)

// get affected ways and relations and update geometries if a node was moved
val elementsWithChangedGeometry = mutableListOf<Element>()
if (edit.action is MoveNodeAction || edit.action is RevertMoveNodeAction) {
val waysContainingNode = getWaysForNode(edit.elementId)
val affectedRelations = getRelationsForNode(edit.elementId) + waysContainingNode.flatMap { getRelationsForWay(it.id) }
for (element in waysContainingNode + affectedRelations) {
val key = ElementKey(element.type, element.id)
deletedElements.remove(key)
updatedElements[key] = element
updatedGeometries[key] = createGeometry(element)
elementsWithChangedGeometry.add(element) // questController needs to know that element was affected (though it was not updated in OSM sense)
}
}
return MapDataUpdates(updated = updates + elementsWithChangedGeometry, deleted = deletedKeys)
}

private fun createGeometry(element: Element): ElementGeometry? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.westnordost.streetcomplete.data.osm.edits.move

import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider
import de.westnordost.streetcomplete.data.osm.edits.IsActionRevertable
import de.westnordost.streetcomplete.data.osm.edits.NewElementsCount
import de.westnordost.streetcomplete.data.osm.edits.update_tags.isGeometrySubstantiallyDifferent
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataChanges
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.upload.ConflictException
import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds
import kotlinx.serialization.Serializable

/** Action that moves a node. */
@Serializable
data class MoveNodeAction(val position: LatLon) : ElementEditAction, IsActionRevertable {

override val newElementsCount get() = NewElementsCount(0, 0, 0)

override fun createUpdates(
originalElement: Element,
element: Element?,
mapDataRepository: MapDataRepository,
idProvider: ElementIdProvider
): MapDataChanges {
val node = element as? Node ?: throw ConflictException("Element deleted")
if (isGeometrySubstantiallyDifferent(originalElement, element)) {
throw ConflictException("Element geometry changed substantially")
}
return MapDataChanges(modifications = listOf(node.copy(
position = position,
timestampEdited = nowAsEpochMilliseconds()
)))
}

override fun createReverted(): ElementEditAction = RevertMoveNodeAction
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package de.westnordost.streetcomplete.data.osm.edits.move

import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider
import de.westnordost.streetcomplete.data.osm.edits.IsRevertAction
import de.westnordost.streetcomplete.data.osm.edits.update_tags.isGeometrySubstantiallyDifferent
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataChanges
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.upload.ConflictException
import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds
import kotlinx.serialization.Serializable

/** Action reverts moving a node. */
@Serializable
object RevertMoveNodeAction : ElementEditAction, IsRevertAction {

override fun createUpdates(
originalElement: Element,
element: Element?,
mapDataRepository: MapDataRepository,
idProvider: ElementIdProvider
): MapDataChanges {
val node = element as? Node ?: throw ConflictException("Element deleted")
return MapDataChanges(modifications = listOf(node.copy(
position = (originalElement as Node).position,
timestampEdited = nowAsEpochMilliseconds()
)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import de.westnordost.streetcomplete.data.osm.edits.AddElementEditsController
import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
import de.westnordost.streetcomplete.data.osm.edits.ElementEditType
import de.westnordost.streetcomplete.data.osm.edits.ElementEditsController
import de.westnordost.streetcomplete.data.osm.edits.MapDataWithEditsSource
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementPointGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementPolylinesGeometry
Expand Down Expand Up @@ -73,6 +74,7 @@ abstract class AbstractOverlayForm :
private val countryInfos: CountryInfos by inject()
private val countryBoundaries: FutureTask<CountryBoundaries> by inject(named("CountryBoundariesFuture"))
private val overlayRegistry: OverlayRegistry by inject()
private val mapDataWithEditsSource: MapDataWithEditsSource by inject()
private val featureDictionaryFuture: FutureTask<FeatureDictionary> by inject(named("FeatureDictionaryFuture"))
protected val featureDictionary: FeatureDictionary get() = featureDictionaryFuture.get()
private var _countryInfo: CountryInfo? = null // lazy but resettable because based on lateinit var
Expand Down Expand Up @@ -141,6 +143,9 @@ abstract class AbstractOverlayForm :
/** Called when the user chose to split the way */
fun onSplitWay(editType: ElementEditType, way: Way, geometry: ElementPolylinesGeometry)

/** Called when the user chose to move the node */
fun onMoveNode(editType: ElementEditType, node: Node)

fun getMapPositionAt(screenPos: Point): LatLon?
}
private val listener: Listener? get() = parentFragment as? Listener ?: activity as? Listener
Expand Down Expand Up @@ -347,6 +352,13 @@ abstract class AbstractOverlayForm :
if (element.isSplittable()) {
answers.add(AnswerItem(R.string.split_way) { splitWay(element) })
}

if (element is Node // add moveNodeAnswer only if it's a free floating node
&& mapDataWithEditsSource.getWaysForNode(element.id).isEmpty()
&& mapDataWithEditsSource.getRelationsForNode(element.id).isEmpty()) {
answers.add(AnswerItem(R.string.move_node) { moveNode() })
}

}

answers.addAll(otherAnswers)
Expand All @@ -357,6 +369,10 @@ abstract class AbstractOverlayForm :
listener?.onSplitWay(overlay, element as Way, geometry as ElementPolylinesGeometry)
}

private fun moveNode() {
listener?.onMoveNode(overlay, element as Node)
}

protected fun composeNote(element: Element) {
val overlayTitle = englishResources.getString(overlay.title)
val leaveNoteContext = "In context of \"$overlayTitle\" overlay"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import de.westnordost.streetcomplete.data.osm.edits.AddElementEditsController
import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
import de.westnordost.streetcomplete.data.osm.edits.ElementEditType
import de.westnordost.streetcomplete.data.osm.edits.ElementEditsController
import de.westnordost.streetcomplete.data.osm.edits.MapDataWithEditsSource
import de.westnordost.streetcomplete.data.osm.edits.delete.DeletePoiNodeAction
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChanges
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChangesBuilder
Expand All @@ -25,6 +26,7 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementPolylinesGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.ElementType
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.osm.mapdata.Way
import de.westnordost.streetcomplete.data.osm.osmquests.HideOsmQuestController
import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType
Expand Down Expand Up @@ -59,6 +61,7 @@ abstract class AbstractOsmQuestForm<T> : AbstractQuestForm(), IsShowingQuestDeta
private val noteEditsController: NoteEditsController by inject()
private val osmQuestController: OsmQuestController by inject()
private val featureDictionaryFuture: FutureTask<FeatureDictionary> by inject(named("FeatureDictionaryFuture"))
private val mapDataWithEditsSource: MapDataWithEditsSource by inject()

protected val featureDictionary: FeatureDictionary get() = featureDictionaryFuture.get()

Expand Down Expand Up @@ -95,6 +98,9 @@ abstract class AbstractOsmQuestForm<T> : AbstractQuestForm(), IsShowingQuestDeta
/** Called when the user chose to split the way */
fun onSplitWay(editType: ElementEditType, way: Way, geometry: ElementPolylinesGeometry)

/** Called when the user chose to move the node */
fun onMoveNode(editType: ElementEditType, node: Node)

/** Called when the user chose to hide the quest instead */
fun onQuestHidden(osmQuestKey: OsmQuestKey)
}
Expand Down Expand Up @@ -139,6 +145,12 @@ abstract class AbstractOsmQuestForm<T> : AbstractQuestForm(), IsShowingQuestDeta
}
createDeleteOrReplaceElementAnswer()?.let { answers.add(it) }

if (element is Node // add moveNodeAnswer only if it's a free floating node
&& mapDataWithEditsSource.getWaysForNode(element.id).isEmpty()
&& mapDataWithEditsSource.getRelationsForNode(element.id).isEmpty()) {
answers.add(AnswerItem(R.string.move_node) { onClickMoveNodeAnswer() })
}

answers.addAll(otherAnswers)
return answers
}
Expand Down Expand Up @@ -195,6 +207,17 @@ abstract class AbstractOsmQuestForm<T> : AbstractQuestForm(), IsShowingQuestDeta
}
}

private fun onClickMoveNodeAnswer() {
context?.let { AlertDialog.Builder(it)
.setMessage(R.string.quest_move_node_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
listener?.onMoveNode(osmElementQuestType, element as Node)
}
.show()
}
}

protected fun applyAnswer(answer: T) {
viewLifecycleScope.launch {
solve(UpdateElementTagsAction(createQuestChanges(answer)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.osm.mapdata.Way
import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuest
import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestHidden
Expand Down Expand Up @@ -77,6 +78,8 @@ import de.westnordost.streetcomplete.screens.HandlesOnBackPressed
import de.westnordost.streetcomplete.screens.main.bottom_sheet.CreateNoteFragment
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsCloseableBottomSheet
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapOrientationAware
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapPositionAware
import de.westnordost.streetcomplete.screens.main.bottom_sheet.MoveNodeFragment
import de.westnordost.streetcomplete.screens.main.bottom_sheet.SplitWayFragment
import de.westnordost.streetcomplete.screens.main.controls.LocationStateButton
import de.westnordost.streetcomplete.screens.main.controls.MainMenuButtonFragment
Expand Down Expand Up @@ -152,6 +155,7 @@ class MainFragment :
NoteDiscussionForm.Listener,
LeaveNoteInsteadFragment.Listener,
CreateNoteFragment.Listener,
MoveNodeFragment.Listener,
EditHistoryFragment.Listener,
MainMenuButtonFragment.Listener,
UndoButtonFragment.Listener,
Expand Down Expand Up @@ -328,6 +332,7 @@ class MainFragment :

val f = bottomSheetFragment
if (f is IsMapOrientationAware) f.onMapOrientation(rotation, tilt)
if (f is IsMapPositionAware) f.onMapMoved(position)
}

override fun onPanBegin() {
Expand Down Expand Up @@ -472,6 +477,24 @@ class MainFragment :
closeBottomSheet()
}

/* ------------------------------- MoveNodeFragment.Listener -------------------------------- */

override fun onMoveNode(editType: ElementEditType, node: Node) {
val mapFragment = mapFragment ?: return
showInBottomSheet(MoveNodeFragment.create(editType, node))
mapFragment.hideNonHighlightedPins()
mapFragment.hideOverlay()

mapFragment.show3DBuildings = false
val offsetPos = mapFragment.getPositionThatCentersPosition(node.position, RectF())
mapFragment.updateCameraPosition { position = offsetPos }
}

override fun onMovedNode(editType: ElementEditType, position: LatLon) {
showQuestSolvedAnimation(editType.icon, position)
closeBottomSheet()
}

/* ------------------------------- ShowsPointMarkers -------------------------------- */

override fun putMarkerForCurrentHighlighting(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.westnordost.streetcomplete.screens.main.bottom_sheet

import de.westnordost.streetcomplete.data.osm.mapdata.LatLon

interface IsMapPositionAware {
fun onMapMoved(position: LatLon)
}
Loading