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 smoothness quest #3257

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
dd1c4a0
add first version of smoothness quest
Helium314 Sep 4, 2021
7586221
update surface quest
Helium314 Sep 6, 2021
abe0146
fix wrong surface form and add smoothness.kt
Helium314 Sep 7, 2021
f65259f
add images
Helium314 Sep 7, 2021
fce2eb9
update strings and adjust layouts a bit
Helium314 Sep 10, 2021
2372f92
remove some unused things, update forms
Helium314 Sep 11, 2021
190ccd4
let the app crash on unknown surface where the quest can't be asked
Helium314 Sep 12, 2021
fec7dfb
small changes as suggested by @westnordost
Helium314 Sep 12, 2021
e7ee462
move some code to newly added SmoothnessItem
Helium314 Sep 12, 2021
675fa23
split smoothness quest into path and road quests
Helium314 Sep 12, 2021
7a71040
add achievements
Helium314 Sep 12, 2021
8d65c2c
fix typo
Helium314 Sep 13, 2021
7ceee01
fix typo, part2
Helium314 Sep 13, 2021
281ad0d
surface quest: remove smoothness if surface value changed on resurvey
Helium314 Sep 13, 2021
2a7ab9b
Merge branch 'smoothness' of https://github.com/Helium314/StreetCompl…
Helium314 Sep 13, 2021
d4c7c40
update images (and strings a little bit)
Helium314 Sep 13, 2021
c47ccd1
update photos and strings
Helium314 Sep 14, 2021
d434da9
remove path tags from road element filter
Helium314 Sep 15, 2021
cec1bf7
update images and strings
Helium314 Sep 16, 2021
766c8ac
Merge branch 'smoothness' of https://github.com/Helium314/StreetCompl…
Helium314 Sep 16, 2021
0bad5ea
update strings and authors
Helium314 Sep 19, 2021
93dc875
update images
Helium314 Sep 22, 2021
e5af15d
update descriptions
Helium314 Oct 2, 2021
594c644
update strings
Helium314 Oct 9, 2021
656aa32
try adding surface to question (not working properly)
Helium314 Oct 9, 2021
469fdd4
Merge branch 'master' into smoothness
westnordost Dec 16, 2021
e65e4ba
remove strings added by botched merge
westnordost Dec 18, 2021
f4e1bc3
Merge branch 'master' into smoothness
westnordost Dec 21, 2021
a8529a0
fix another merge issue
westnordost Dec 21, 2021
8d63996
formatting + only show smoothness quest for the same roads the surfac…
westnordost Dec 21, 2021
2ef1825
use computed properties instead of functions where possible
westnordost Dec 21, 2021
44e0102
(provisionally) change title wording so that it does not contradict t…
westnordost Dec 21, 2021
de00a4f
remove unfinished feature that doesn't work
westnordost Dec 21, 2021
2f09e33
add vehicle-type emojis
westnordost Dec 21, 2021
9dc3d80
fix another problem after merge
westnordost Dec 21, 2021
92fe735
refine wordings
westnordost Dec 22, 2021
640367a
select new image for bad asphalt, 3:2 for intermediate and bad asphalt
westnordost Dec 22, 2021
c368c72
better picture for excellent paving stones
westnordost Dec 23, 2021
4f98420
further refine wording for sett after conferring in #language channel…
westnordost Dec 23, 2021
66aff91
intermediate paving stones 3:2
westnordost Dec 23, 2021
6c05903
add hint that it is about the big picture, not some details
westnordost Dec 29, 2021
f078974
refine descriptions a bit more (include mention of "rough asphalt")
westnordost Dec 29, 2021
58815c3
very bad sett 3:2
westnordost Dec 29, 2021
012bd4d
replace image for intermediate sett
westnordost Dec 29, 2021
9bd7e8a
replace image for good sett
westnordost Dec 29, 2021
6b52ad9
replace image for bad sett
westnordost Dec 29, 2021
f802eef
bad sett 3:2
westnordost Dec 30, 2021
39ca513
larger cutout of original photo for context
westnordost Dec 30, 2021
4e8f759
better image for very bad asphalt
westnordost Dec 30, 2021
6ab0c7b
add picture for very bad paving stones
westnordost Dec 30, 2021
36e6445
excellent asphalt 3:2
westnordost Dec 30, 2021
191163a
good asphalt 3:2
westnordost Dec 30, 2021
83feb06
compacted 3:2
westnordost Jan 1, 2022
b92754e
paving stones intermediate 3:2
westnordost Jan 1, 2022
4cc7ab2
intermediate compacted 3:2
westnordost Jan 1, 2022
3aea27f
very bad compacted 3:2
westnordost Jan 1, 2022
0a67489
intermediate and bad gravel 3:2
westnordost Jan 1, 2022
22344e6
add changelog entry to credit the main contributors
westnordost Jan 1, 2022
472a899
replace very bad gravel photo
westnordost Jan 1, 2022
2438369
use same picture for horrible gravel and horrible compacted
westnordost Jan 1, 2022
bbf8303
very horrible unpaved 3:2
westnordost Jan 1, 2022
588d652
also remove smoothness if surface is wrong
westnordost Jan 1, 2022
b3f14d0
descriptions for compacted etc
westnordost Jan 2, 2022
13cef40
3:2 impassable with less weird image
westnordost Jan 2, 2022
73cb663
update licenses
westnordost Jan 3, 2022
7437c79
Merge branch 'master' into smoothness
westnordost Jan 3, 2022
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 @@ -105,6 +105,7 @@ import de.westnordost.streetcomplete.quests.shop_type.CheckShopType
import de.westnordost.streetcomplete.quests.shop_type.SpecifyShopType
import de.westnordost.streetcomplete.quests.shoulder.AddShoulder
import de.westnordost.streetcomplete.quests.sidewalk.AddSidewalk
import de.westnordost.streetcomplete.quests.smoothness.*
import de.westnordost.streetcomplete.quests.sport.AddSport
import de.westnordost.streetcomplete.quests.steps_incline.AddStepsIncline
import de.westnordost.streetcomplete.quests.steps_ramp.AddStepsRamp
Expand Down Expand Up @@ -358,6 +359,8 @@ import javax.inject.Singleton
AddLanes(), // abstreet, certainly most routing engines - often requires way to be split
AddStreetParking(),
AddShoulder(),
AddRoadSmoothness(),
AddPathSmoothness(),

// footways
AddPathSurface(), // used by OSM Carto, BRouter, OsmAnd, OSRM, graphhopper...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.westnordost.streetcomplete.quests.smoothness

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey
import de.westnordost.streetcomplete.data.meta.updateWithCheckDate
import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChangesBuilder
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.WHEELCHAIR
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.BICYCLIST
import de.westnordost.streetcomplete.ktx.arrayOfNotNull

class AddPathSmoothness : OsmFilterQuestType<SmoothnessAnswer>() {

override val elementFilter = """
ways with
highway ~ ${ALL_PATHS_EXCEPT_STEPS.joinToString("|")}
and surface ~ ${SURFACES_FOR_SMOOTHNESS.joinToString("|")}
and access !~ private|no
and segregated != yes
and (!conveying or conveying = no)
and (!indoor or indoor = no)
and !cycleway:surface and !footway:surface
and (
!smoothness
or smoothness older today -4 years
)
"""

override val commitMessage = "Add path smoothness"
override val wikiLink = "Key:smoothness"
override val icon = R.drawable.ic_quest_path_surface_detail
override val isSplitWayEnabled = true
override val questTypeAchievements = listOf(WHEELCHAIR, BICYCLIST)

override fun getTitle(tags: Map<String, String>): Int {
val hasName = tags.containsKey("name")
val isSquare = tags["area"] == "yes"
return when {
hasName -> R.string.quest_smoothness_name_title
isSquare -> R.string.quest_smoothness_square_title
else -> R.string.quest_smoothness_path_title
}
}

override fun getTitleArgs(tags: Map<String, String>, featureName: Lazy<String?>): Array<String> =
arrayOfNotNull(tags["name"])

override fun createForm() = AddSmoothnessForm()

override fun applyAnswerTo(answer: SmoothnessAnswer, changes: StringMapChangesBuilder) {
when (answer) {
is SmoothnessValueAnswer -> changes.updateWithCheckDate("smoothness", answer.osmValue)
is WrongSurfaceAnswer -> {
changes.delete("surface")
changes.deleteIfExists("smoothness")
changes.deleteCheckDatesForKey("smoothness")
}
}
}
}

// smoothness is not asked for steps
// "pedestrian" is in here so the path answers are shown instead of road answers (which focus on cars)
val ALL_PATHS_EXCEPT_STEPS = listOf("footway", "cycleway", "path", "bridleway", "pedestrian")
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package de.westnordost.streetcomplete.quests.smoothness

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey
import de.westnordost.streetcomplete.data.meta.updateWithCheckDate
import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChangesBuilder
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.CAR
import de.westnordost.streetcomplete.data.user.achievements.QuestTypeAchievement.BICYCLIST
import de.westnordost.streetcomplete.ktx.arrayOfNotNull

class AddRoadSmoothness : OsmFilterQuestType<SmoothnessAnswer>() {

override val elementFilter = """
ways with (
highway ~ ${ROADS_TO_ASK_SMOOTHNESS_FOR.joinToString("|")}
or highway = service and service !~ driveway|slipway
)
and surface ~ ${SURFACES_FOR_SMOOTHNESS.joinToString("|")}
and (access !~ private|no or (foot and foot !~ private|no))
and (
!smoothness
or smoothness older today -4 years
)
"""

override val commitMessage = "Add road smoothness"
override val wikiLink = "Key:smoothness"
override val icon = R.drawable.ic_quest_street_surface_detail
override val isSplitWayEnabled = true
override val questTypeAchievements = listOf(CAR, BICYCLIST)

override fun getTitle(tags: Map<String, String>): Int {
val hasName = tags.containsKey("name")
val isSquare = tags["area"] == "yes"
return when {
hasName -> R.string.quest_smoothness_name_title
isSquare -> R.string.quest_smoothness_square_title
else -> R.string.quest_smoothness_road_title
}
}

override fun getTitleArgs(tags: Map<String, String>, featureName: Lazy<String?>): Array<String> =
arrayOfNotNull(tags["name"])

override fun createForm() = AddSmoothnessForm()

override fun applyAnswerTo(answer: SmoothnessAnswer, changes: StringMapChangesBuilder) {
when (answer) {
is SmoothnessValueAnswer -> changes.updateWithCheckDate("smoothness", answer.osmValue)
is WrongSurfaceAnswer -> {
changes.delete("surface")
changes.deleteIfExists("smoothness")
changes.deleteCheckDatesForKey("smoothness")
}
}
}
}

// surfaces that are actually used in AddSmoothnessForm
// should only contain values that are in the Surface class
val SURFACES_FOR_SMOOTHNESS = listOf(
"asphalt", "sett", "paving_stones", "compacted", "gravel", "fine_gravel"
)

private val ROADS_TO_ASK_SMOOTHNESS_FOR = arrayOf(
// "trunk","trunk_link","motorway","motorway_link", // too much, motorways are almost by definition smooth asphalt (or concrete)
"primary", "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link",
"unclassified", "residential", "living_street", "pedestrian", "track",
// "service", // this is too much, and the information value is very low
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.westnordost.streetcomplete.quests.smoothness

import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.quests.AImageListQuestAnswerFragment
import de.westnordost.streetcomplete.quests.AnswerItem
import de.westnordost.streetcomplete.quests.surface.Surface
import de.westnordost.streetcomplete.quests.surface.asItem
import de.westnordost.streetcomplete.view.image_select.ItemViewHolder

class AddSmoothnessForm : AImageListQuestAnswerFragment<Smoothness, SmoothnessAnswer>() {

override val descriptionResId = R.string.quest_smoothness_hint

override val otherAnswers = listOf(
AnswerItem(R.string.quest_smoothness_wrong_surface) { surfaceWrong() },
AnswerItem(R.string.quest_smoothness_obstacle) { showObstacleHint() }
)

private val surfaceTag get() = osmElement!!.tags["surface"]

override val items get() = Smoothness.values().toItems(requireContext(), surfaceTag!!)

override val itemsPerRow = 1

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
imageSelector.cellLayoutId = R.layout.cell_labeled_icon_select_smoothness
}

override val moveFavoritesToFront = false

override fun onClickOk(selectedItems: List<Smoothness>) {
applyAnswer(SmoothnessValueAnswer(selectedItems.single().osmValue))
}

private fun showObstacleHint() {
activity?.let { AlertDialog.Builder(it)
.setMessage(R.string.quest_smoothness_obstacle_hint)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}

private fun surfaceWrong() {
val surfaceType = Surface.values().find { it.osmValue == surfaceTag }!!
showWrongSurfaceDialog(surfaceType)
}

private fun showWrongSurfaceDialog(surface: Surface) {
val inflater = LayoutInflater.from(requireContext())
val inner = inflater.inflate(R.layout.dialog_quest_smoothness_wrong_surface, null, false)
ItemViewHolder(inner.findViewById(R.id.item_view)).bind(surface.asItem())

AlertDialog.Builder(requireContext())
.setView(inner)
.setPositiveButton(R.string.quest_generic_hasFeature_yes_leave_note) { _, _ -> composeNote() }
.setNegativeButton(R.string.quest_generic_hasFeature_no) { _, _ -> applyAnswer(WrongSurfaceAnswer) }
.show()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package de.westnordost.streetcomplete.quests.smoothness

enum class Smoothness(val osmValue: String) {
EXCELLENT("excellent"),
GOOD("good"),
INTERMEDIATE("intermediate"),
BAD("bad"),
VERY_BAD("very_bad"),
HORRIBLE("horrible"),
VERY_HORRIBLE("very_horrible"),
IMPASSABLE("impassable"),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.westnordost.streetcomplete.quests.smoothness

sealed class SmoothnessAnswer

data class SmoothnessValueAnswer(val osmValue: String): SmoothnessAnswer()

object WrongSurfaceAnswer: SmoothnessAnswer()
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package de.westnordost.streetcomplete.quests.smoothness

import android.content.Context
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.quests.smoothness.Smoothness.*
import de.westnordost.streetcomplete.view.CharSequenceText
import de.westnordost.streetcomplete.view.ResImage
import de.westnordost.streetcomplete.view.ResText
import de.westnordost.streetcomplete.view.image_select.DisplayItem
import de.westnordost.streetcomplete.view.image_select.Item2

fun Array<Smoothness>.toItems(context: Context, surface: String) =
mapNotNull { it.asItem(context, surface) }

// return null if not a valid combination
fun Smoothness.asItem(context: Context, surface: String): DisplayItem<Smoothness>? {
val imageResId = getImageResId(surface) ?: return null
val descriptionResId = getDescriptionResId(surface) ?: return null
return Item2(
this,
ResImage(imageResId),
CharSequenceText(context.getString(titleResId) + " " + emoji),
ResText(descriptionResId)
)
}

/** return fitting vehicle type emoji that corresponds to the "usable by" column in the wiki */
val Smoothness.emoji get() = when(this) {
EXCELLENT -> """🛹""" // or 🛼 but it is only available since Android 11
GOOD -> """🛴""" // no emoji for racing bike, would be difficult to tell apart from 🚲
INTERMEDIATE -> """🚲""" // or 🛵 but users are more likely to own a bike than a scooter
BAD -> """🚗""" // or 🛺 but tuk-tuks have actually similar requirements as scooters
VERY_BAD -> """🚙""" // this is a SUV
HORRIBLE -> """🛻""" // no emoji for off-road vehicles but there is one for pick-ups (Android 11)
VERY_HORRIBLE -> """🚜"""
IMPASSABLE -> """🚶"""
}

val Smoothness.titleResId get() = when (this) {
EXCELLENT -> R.string.quest_smoothness_title_excellent
GOOD -> R.string.quest_smoothness_title_good
INTERMEDIATE -> R.string.quest_smoothness_title_intermediate
BAD -> R.string.quest_smoothness_title_bad
VERY_BAD -> R.string.quest_smoothness_title_very_bad
HORRIBLE -> R.string.quest_smoothness_title_horrible
VERY_HORRIBLE -> R.string.quest_smoothness_title_very_horrible
IMPASSABLE -> R.string.quest_smoothness_title_impassable
}

fun Smoothness.getDescriptionResId(surface: String): Int? = when (surface) {
"asphalt", "concrete" -> pavedDescriptionResId
"sett" -> settDescriptionResId
"paving_stones" -> pavingStonesDescriptionResId
"compacted", "gravel", "fine_gravel" -> compactedOrGravelDescriptionResId
else -> null
} ?: descriptionResIdFallback

private val Smoothness.descriptionResIdFallback: Int? get() = when(this) {
HORRIBLE -> R.string.quest_smoothness_description_horrible
VERY_HORRIBLE -> R.string.quest_smoothness_description_very_horrible
IMPASSABLE -> R.string.quest_smoothness_description_impassable
else -> null
}

fun Smoothness.getImageResId(surface: String): Int? = when(surface) {
"asphalt" -> asphaltImageResId
"sett" -> settImageResId
"paving_stones" -> pavingStonesImageResId
"compacted" -> compactedImageResId
"gravel" -> gravelImageResId
else -> null
}

private val Smoothness.asphaltImageResId get() = when (this) {
EXCELLENT -> R.drawable.surface_asphalt_excellent
GOOD -> R.drawable.surface_asphalt_good
INTERMEDIATE -> R.drawable.surface_asphalt_intermediate
BAD -> R.drawable.surface_asphalt_bad
VERY_BAD -> R.drawable.surface_asphalt_very_bad
else -> null
}

private val Smoothness.pavedDescriptionResId get() = when(this) {
EXCELLENT -> R.string.quest_smoothness_description_excellent_paved
GOOD -> R.string.quest_smoothness_description_good_paved
INTERMEDIATE -> R.string.quest_smoothness_description_intermediate_paved
BAD -> R.string.quest_smoothness_description_bad_paved
VERY_BAD -> R.string.quest_smoothness_description_very_bad_paved
else -> null
}

private val Smoothness.settImageResId get() = when (this) {
GOOD -> R.drawable.surface_sett_good
INTERMEDIATE -> R.drawable.surface_sett_intermediate
BAD -> R.drawable.surface_sett_bad
VERY_BAD -> R.drawable.surface_sett_very_bad
else -> null
}

private val Smoothness.settDescriptionResId get() = when(this) {
GOOD -> R.string.quest_smoothness_description_good_sett
INTERMEDIATE -> R.string.quest_smoothness_description_intermediate_sett
BAD -> R.string.quest_smoothness_description_bad_sett
VERY_BAD -> R.string.quest_smoothness_description_very_bad_sett
else -> null
}

private val Smoothness.pavingStonesImageResId get() = when (this) {
EXCELLENT -> R.drawable.surface_paving_stones_excellent
GOOD -> R.drawable.surface_paving_stones_good
INTERMEDIATE -> R.drawable.surface_paving_stones_intermediate
BAD -> R.drawable.surface_paving_stones_bad
VERY_BAD -> R.drawable.surface_paving_stones_very_bad
else -> null
}

private val Smoothness.pavingStonesDescriptionResId get() = when(this) {
EXCELLENT -> R.string.quest_smoothness_description_excellent_paving_stones
GOOD -> R.string.quest_smoothness_description_good_paving_stones
INTERMEDIATE -> R.string.quest_smoothness_description_intermediate_paving_stones
BAD -> R.string.quest_smoothness_description_bad_paving_stones
VERY_BAD -> R.string.quest_smoothness_description_very_bad_paving_stones
else -> null
}

private val Smoothness.compactedImageResId get() = when (this) {
INTERMEDIATE -> R.drawable.surface_compacted_intermediate
BAD -> R.drawable.surface_compacted_bad
VERY_BAD -> R.drawable.surface_compacted_very_bad
HORRIBLE -> R.drawable.surface_unpaved_horrible
VERY_HORRIBLE -> R.drawable.surface_unpaved_very_horrible
IMPASSABLE -> R.drawable.surface_unpaved_impassable
else -> null
}

private val Smoothness.gravelImageResId get() = when (this) {
INTERMEDIATE -> R.drawable.surface_gravel_intermediate
BAD -> R.drawable.surface_gravel_bad
VERY_BAD -> R.drawable.surface_gravel_very_bad
HORRIBLE -> R.drawable.surface_unpaved_horrible
VERY_HORRIBLE -> R.drawable.surface_unpaved_very_horrible
IMPASSABLE -> R.drawable.surface_unpaved_impassable
else -> null
}

private val Smoothness.compactedOrGravelDescriptionResId get() = when(this) {
INTERMEDIATE -> R.string.quest_smoothness_description_intermediate_compacted_gravel
BAD -> R.string.quest_smoothness_description_bad_compacted_gravel
VERY_BAD -> R.string.quest_smoothness_description_very_bad_compacted_gravel
else -> null
}
Loading