Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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,54 @@
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" -> LeftAndRightSidewalk(left = NO, right = 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 @@ -118,6 +118,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 @@ -387,6 +388,7 @@ import javax.inject.Singleton
AddCyclewaySegregation(), // Cyclosm, Valhalla, Bike Citizens Bicycle Navigation...
AddFootwayPartSurface(),
AddCyclewayPartSurface(),
AddSidewalkSurface(),

/* should best be after road surface because it excludes unpaved roads, also, need to search
* for the sign which is one reason why it is disabled by default */
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 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,144 @@
package de.westnordost.streetcomplete.quests.surface

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED
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.meta.updateWithCheckDate
import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType
import de.westnordost.streetcomplete.data.osm.osmquests.Tags
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.OUTDOORS
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.PEDESTRIAN
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.WHEELCHAIR

class AddSidewalkSurface : OsmFilterQuestType<SidewalkSurfaceAnswer>() {

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 or sidewalk = left or sidewalk = right 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:both:surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and sidewalk:both:surface older today -4 years
or sidewalk:left:surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and sidewalk:left:surface older today -4 years
or sidewalk:right:surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and sidewalk:right:surface older today -4 years
or ${SIDEWALK_SURFACE_KEYS.joinToString(" or ") {"$it older today -8 years"}}
or (
sidewalk:both:surface ~ paved|unpaved|cobblestone
and !sidewalk:both:surface:note
and !note:sidewalk:both:surface
)
or (
sidewalk:left:surface ~ paved|unpaved|cobblestone
and !sidewalk:left:surface:note
and !note:sidewalk:left:surface
)
or (
sidewalk:right:surface ~ paved|unpaved|cobblestone
and !sidewalk:right:surface:note
and !note:sidewalk:right:surface
)
)
"""

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, OUTDOORS)
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
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 previousSideValue = tags["sidewalk:${side.value}:surface"]
val previousBothOsmValue = tags["sidewalk:both:surface"]
val osmValue = surface.value.osmValue

return if (previousSideValue != null && previousSideValue != osmValue) {
true
} else 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