Skip to content

Commit

Permalink
Merge pull request #3735 from arrival-spring/sidewalk-surface
Browse files Browse the repository at this point in the history
Sidewalk surface quest
  • Loading branch information
westnordost authored Mar 4, 2022
2 parents e230b76 + 71a523d commit f3bf845
Show file tree
Hide file tree
Showing 17 changed files with 782 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ val MAXSPEED_TYPE_KEYS = setOf(
"zone:traffic"
)

val SIDEWALK_SURFACE_KEYS = setOf(
"sidewalk:both:surface",
"sidewalk:left:surface",
"sidewalk:right:surface"
)

const val SURVEY_MARK_KEY = "check_date"

// generated by "make update" from https://github.com/mnalis/StreetComplete-taginfo-categorize/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package de.westnordost.streetcomplete.quests.sidewalk
package de.westnordost.streetcomplete.osm.sidewalk

import de.westnordost.streetcomplete.data.osm.osmquests.Tags
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.NO
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.SEPARATE
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.YES
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.NO
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.SEPARATE
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.YES

data class SidewalkSides(val left: Sidewalk, val right: Sidewalk)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package de.westnordost.streetcomplete.osm.sidewalk

import de.westnordost.streetcomplete.ktx.containsAny
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.NO
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.SEPARATE
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.YES


data class LeftAndRightSidewalk(val left: Sidewalk?, val right: Sidewalk?)

/** Returns on which sides are sidewalks. Returns null if tagging is unknown */
fun createSidewalkSides(tags: Map<String, String>): LeftAndRightSidewalk? {
if (!tags.keys.containsAny(KNOWN_SIDEWALK_KEYS)) return null

val sidewalk = createSidewalksDefault(tags)
if (sidewalk != null) return sidewalk

// alternative tagging
val altSidewalk = createSidewalksAlternative(tags)
if (altSidewalk != null) return altSidewalk

return null
}

private fun createSidewalksDefault(tags: Map<String, String>): LeftAndRightSidewalk? = when(tags["sidewalk"]) {
"left" -> LeftAndRightSidewalk(left = YES, right = NO)
"right" -> LeftAndRightSidewalk(left = NO, right = YES)
"both" -> LeftAndRightSidewalk(left = YES, right = YES)
"no", "none" -> LeftAndRightSidewalk(left = NO, right = NO)
"separate" -> LeftAndRightSidewalk(left = SEPARATE, right = SEPARATE)
else -> null
}

private fun createSidewalksAlternative(tags: Map<String, String>): LeftAndRightSidewalk? {
val sidewalkLeft = tags["sidewalk:both"] ?: tags["sidewalk:left"]
val sidewalkRight = tags["sidewalk:both"] ?: tags["sidewalk:right"]
return if (sidewalkLeft != null || sidewalkRight != null) {
LeftAndRightSidewalk(left = createSidewalkSide(sidewalkLeft), right = createSidewalkSide(sidewalkRight))
} else {
null
}
}

private fun createSidewalkSide(tag: String?): Sidewalk? = when(tag) {
"yes" -> YES
"no" -> NO
"separate" -> SEPARATE
else -> null
}

private val KNOWN_SIDEWALK_KEYS = listOf(
"sidewalk", "sidewalk:left", "sidewalk:right", "sidewalk:both"
)
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ import de.westnordost.streetcomplete.quests.surface.AddFootwayPartSurface
import de.westnordost.streetcomplete.quests.surface.AddPathSurface
import de.westnordost.streetcomplete.quests.surface.AddPitchSurface
import de.westnordost.streetcomplete.quests.surface.AddRoadSurface
import de.westnordost.streetcomplete.quests.surface.AddSidewalkSurface
import de.westnordost.streetcomplete.quests.tactile_paving.AddTactilePavingBusStop
import de.westnordost.streetcomplete.quests.tactile_paving.AddTactilePavingCrosswalk
import de.westnordost.streetcomplete.quests.tactile_paving.AddTactilePavingKerb
Expand Down Expand Up @@ -406,6 +407,7 @@ whether the postbox is still there in countries in which it is enabled */
AddCyclewaySegregation(), // Cyclosm, Valhalla, Bike Citizens Bicycle Navigation...
AddFootwayPartSurface(),
AddCyclewayPartSurface(),
AddSidewalkSurface(),
AddCyclewayWidth(arSupportChecker), // should be after cycleway segregation

/* should best be after road surface because it excludes unpaved roads, also, need to search
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import de.westnordost.streetcomplete.osm.estimateCycleTrackWidth
import de.westnordost.streetcomplete.osm.estimateParkingOffRoadWidth
import de.westnordost.streetcomplete.osm.estimateRoadwayWidth
import de.westnordost.streetcomplete.osm.guessRoadwayWidth
import de.westnordost.streetcomplete.osm.sidewalk.SidewalkSides
import de.westnordost.streetcomplete.osm.sidewalk.applyTo
import de.westnordost.streetcomplete.util.isNearAndAligned

class AddSidewalk : OsmElementQuestType<SidewalkSides> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.westnordost.streetcomplete.quests.sidewalk

import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk
import de.westnordost.streetcomplete.osm.sidewalk.SidewalkSides
import androidx.appcompat.app.AlertDialog
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.quests.AStreetSideSelectFragment
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package de.westnordost.streetcomplete.quests.sidewalk

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk
import de.westnordost.streetcomplete.quests.StreetSideDisplayItem
import de.westnordost.streetcomplete.quests.StreetSideItem
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.NO
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.SEPARATE
import de.westnordost.streetcomplete.quests.sidewalk.Sidewalk.YES
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.NO
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.SEPARATE
import de.westnordost.streetcomplete.osm.sidewalk.Sidewalk.YES
import de.westnordost.streetcomplete.view.image_select.DisplayItem
import de.westnordost.streetcomplete.view.image_select.Item

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package de.westnordost.streetcomplete.quests.surface

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.meta.SIDEWALK_SURFACE_KEYS
import de.westnordost.streetcomplete.data.meta.hasCheckDateForKey
import de.westnordost.streetcomplete.data.meta.removeCheckDatesForKey
import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey
import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType
import de.westnordost.streetcomplete.data.osm.osmquests.Tags
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.PEDESTRIAN
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.WHEELCHAIR

class AddSidewalkSurface : OsmFilterQuestType<SidewalkSurfaceAnswer>() {

// Only roads with 'complete' sidewalk tagging (at least one side has sidewalk, other side specified)
override val elementFilter = """
ways with
highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential
and area != yes
and motorroad != yes
and (
sidewalk ~ both|left|right or
sidewalk:both = yes or
(sidewalk:left = yes and sidewalk:right ~ yes|no|separate) or
(sidewalk:right = yes and sidewalk:left ~ yes|no|separate)
)
and (
${SIDEWALK_SURFACE_KEYS.joinToString(" and ") {"!$it"}}
or sidewalk:surface older today -8 years
)
"""

override val changesetComment = "Add surface of sidewalks"
override val wikiLink = "Key:sidewalk"
override val icon = R.drawable.ic_quest_sidewalk_surface
override val isSplitWayEnabled = true
override val questTypeAchievements = listOf(PEDESTRIAN, WHEELCHAIR)
override val defaultDisabledMessage = R.string.default_disabled_msg_difficult_and_time_consuming

override fun getTitle(tags: Map<String, String>) : Int =
R.string.quest_sidewalk_surface_title

override fun createForm() = AddSidewalkSurfaceForm()

override fun applyAnswerTo(answer: SidewalkSurfaceAnswer, tags: Tags, timestampEdited: Long) {
val leftChanged = answer.left?.let { sideSurfaceChanged(it, Side.LEFT, tags) }
val rightChanged = answer.right?.let { sideSurfaceChanged(it, Side.RIGHT, tags) }

if (leftChanged == true) {
deleteSmoothnessKeys(Side.LEFT, tags)
deleteSmoothnessKeys(Side.BOTH, tags)
}
if (rightChanged == true) {
deleteSmoothnessKeys(Side.RIGHT, tags)
deleteSmoothnessKeys(Side.BOTH, tags)
}

if (answer.left == answer.right) {
answer.left?.let { applySidewalkSurfaceAnswerTo(it, Side.BOTH, tags) }
deleteSidewalkSurfaceAnswerIfExists(Side.LEFT, tags)
deleteSidewalkSurfaceAnswerIfExists(Side.RIGHT, tags)
} else {
answer.left?.let { applySidewalkSurfaceAnswerTo(it, Side.LEFT, tags) }
answer.right?.let { applySidewalkSurfaceAnswerTo(it, Side.RIGHT, tags) }
deleteSidewalkSurfaceAnswerIfExists(Side.BOTH, tags)
}
deleteSidewalkSurfaceAnswerIfExists(null, tags)

// only set the check date if nothing was changed or if check date was already set
if (!tags.hasChanges || tags.hasCheckDateForKey("sidewalk:surface")) {
tags.updateCheckDateForKey("sidewalk:surface")
}
}

private enum class Side(val value: String) {
LEFT("left"), RIGHT("right"), BOTH("both")
}

private fun sideSurfaceChanged(surface: SurfaceAnswer, side: Side, tags: Tags): Boolean {
val previousSideOsmValue = tags["sidewalk:${side.value}:surface"]
val previousBothOsmValue = tags["sidewalk:both:surface"]
val osmValue = surface.value.osmValue

return previousSideOsmValue != null && previousSideOsmValue != osmValue
|| previousBothOsmValue != null && previousBothOsmValue != osmValue
}

private fun applySidewalkSurfaceAnswerTo(surface: SurfaceAnswer, side: Side, tags: Tags)
{
val sidewalkKey = "sidewalk:" + side.value
val sidewalkSurfaceKey = "$sidewalkKey:surface"

tags[sidewalkSurfaceKey] = surface.value.osmValue

// add/remove note - used to describe generic surfaces
if (surface.note != null) {
tags["$sidewalkSurfaceKey:note"] = surface.note
} else {
tags.remove("$sidewalkSurfaceKey:note")
}
// clean up old source tags - source should be in changeset tags
tags.remove("source:$sidewalkSurfaceKey")
}

/** clear smoothness tags for the given side*/
private fun deleteSmoothnessKeys(side: Side, tags: Tags) {
val sidewalkKey = "sidewalk:" + side.value
tags.remove("$sidewalkKey:smoothness")
tags.remove("$sidewalkKey:smoothness:date")
tags.removeCheckDatesForKey("$sidewalkKey:smoothness")
}

/** clear previous answers for the given side */
private fun deleteSidewalkSurfaceAnswerIfExists(side: Side?, tags: Tags) {
val sideVal = if (side == null) "" else ":" + side.value
val sidewalkSurfaceKey = "sidewalk$sideVal:surface"

// only things are cleared that are set by this quest
// for example cycleway:surface should only be cleared by a cycleway surface quest etc.
tags.remove(sidewalkSurfaceKey)
tags.remove("$sidewalkSurfaceKey:note")
}

}
Loading

0 comments on commit f3bf845

Please sign in to comment.