Skip to content

Commit

Permalink
add quest to make surface=paved/unpaved roads more detailed
Browse files Browse the repository at this point in the history
road part of streetcomplete#279
  • Loading branch information
matkoniecz committed Jun 23, 2020
1 parent a97cd9e commit 5c47912
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ import de.westnordost.streetcomplete.quests.localized_name.data.RoadNameSuggesti
import de.westnordost.streetcomplete.quests.segregated.AddCyclewaySegregation
import de.westnordost.streetcomplete.quests.self_service.AddSelfServiceLaundry
import de.westnordost.streetcomplete.quests.sidewalk.AddSidewalk
import de.westnordost.streetcomplete.quests.surface.AddCyclewayPartSurface
import de.westnordost.streetcomplete.quests.surface.AddFootwayPartSurface
import de.westnordost.streetcomplete.quests.surface.AddPathSurface
import de.westnordost.streetcomplete.quests.tactile_paving.AddTactilePavingBusStop
import de.westnordost.streetcomplete.quests.tactile_paving.AddTactilePavingCrosswalk
import de.westnordost.streetcomplete.quests.toilet_availability.AddToiletAvailability
Expand All @@ -74,7 +71,6 @@ import de.westnordost.streetcomplete.quests.address.AddHousenumber
import de.westnordost.streetcomplete.quests.max_speed.AddMaxSpeed
import de.westnordost.streetcomplete.quests.opening_hours.AddOpeningHours
import de.westnordost.streetcomplete.quests.localized_name.AddRoadName
import de.westnordost.streetcomplete.quests.surface.AddRoadSurface
import de.westnordost.streetcomplete.quests.roof_shape.AddRoofShape
import de.westnordost.streetcomplete.quests.sport.AddSport
import de.westnordost.streetcomplete.quests.traffic_signals_sound.AddTrafficSignalsSound
Expand All @@ -87,6 +83,7 @@ import de.westnordost.streetcomplete.quests.bench_backrest.AddBenchBackrest
import de.westnordost.streetcomplete.quests.wheelchair_access.AddWheelchairAccessOutside
import de.westnordost.streetcomplete.quests.ferry.AddFerryAccessMotorVehicle
import de.westnordost.streetcomplete.quests.ferry.AddFerryAccessPedestrian
import de.westnordost.streetcomplete.quests.surface.*
import de.westnordost.streetcomplete.quests.wheelchair_access.AddWheelchairAccessToiletsPart

@Module
Expand Down Expand Up @@ -124,6 +121,7 @@ object QuestModule
AddRailwayCrossingBarrier(o), // useful for routing
AddPostboxCollectionTimes(o),
AddOpeningHours(o, featureDictionaryFuture),
DetailRoadSurface(o), // BRouter, OsmAnd, OSRM, graphhopper
AddBikeParkingCapacity(o), // cycle map layer on osm.org
AddOrchardProduce(o),
AddBuildingType(o), // because housenumber, building levels etc. depend on it
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package de.westnordost.streetcomplete.quests.surface

import android.util.Log
import de.westnordost.osmapi.map.data.BoundingBox
import de.westnordost.osmapi.map.data.Element
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder
import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi
import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType
import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType
import de.westnordost.streetcomplete.data.quest.QuestStatus
import de.westnordost.streetcomplete.data.tagfilters.FiltersParser
import de.westnordost.streetcomplete.data.tagfilters.getQuestPrintStatement
import de.westnordost.streetcomplete.data.tagfilters.toGlobalOverpassBBox
import de.westnordost.streetcomplete.quests.address.PlaceName
import de.westnordost.streetcomplete.quests.address.StreetName
import de.westnordost.streetcomplete.quests.localized_name.AddRoadName


class DetailRoadSurface(private val overpassMapDataApi: OverpassMapDataAndGeometryApi) : OsmElementQuestType<DetailSurfaceAnswer> {
override val commitMessage = "Add more detailed surfaces"
override val wikiLink = "Key:surface"
override val icon = R.drawable.ic_quest_street_surface_paved_detail // TODO: consider changing icon name or restricting to surface=paved

override fun getTitle(tags: Map<String, String>): Int {
val hasName = tags.containsKey("name")
val isSquare = tags["area"] == "yes"

return if (hasName) {
if (isSquare)
R.string.quest_streetSurface_square_name_title
else
R.string.quest_streetSurface_name_title
} else {
if (isSquare)
R.string.quest_streetSurface_square_title
else
R.string.quest_streetSurface_title
}
}

override fun createForm() = DetailRoadSurfaceForm()

override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean {
return overpassMapDataApi.query(getOverpassQuery(bbox), handler)
}

override fun isApplicableTo(element: Element): Boolean? {
if(!REQUIRED_MINIMAL_MATCH_TFE.matches(element)) {
return false;
}
element.tags.forEach {
if(it.key.contains("surface:")) {
return false;
}
if(it.key.contains(":surface")) {
return false;
}
}
return true;
}

private fun getOverpassQuery(bbox: BoundingBox) =
bbox.toGlobalOverpassBBox() + "\n" + """
way[surface~"^(${UNDETAILED_SURFACE_TAG_MATCH})${'$'}"][segregated!="yes"][highway ~ "^${ HIGHWAY_TAG_MATCH }${'$'}"] -> .surface_without_detail;
// https://taginfo.openstreetmap.org//search?q=%3Asurface
// https://taginfo.openstreetmap.org//search?q=surface:
way[~"(:surface|surface:)"~"."] -> .extra_tags;
(.surface_without_detail; - .extra_tags;);
""".trimIndent() + "\n" +
getQuestPrintStatement()

private val HIGHWAY_TAG_MATCH = ROADS_WITH_SURFACES_BROADLY_DEFINED.joinToString("|")
private val UNDETAILED_SURFACE_TAG_MATCH = "paved|unpaved"
private val REQUIRED_MINIMAL_MATCH_TFE by lazy { FiltersParser().parse(
"ways with surface ~ ${UNDETAILED_SURFACE_TAG_MATCH} and segregated!=yes and highway ~ ${HIGHWAY_TAG_MATCH}"
)}

override val isSplitWayEnabled = true

override fun applyAnswerTo(answer: DetailSurfaceAnswer, changes: StringMapChangesBuilder) {
when(answer) {
is SurfaceAnswer -> {
changes.modify("surface", answer.value)
}
is DetailingImpossibleAnswer -> {
changes.add("surface:note", answer.value)
}
}
}

companion object {
// well, all roads have surfaces, what I mean is that not all ways with highway key are
// "something with a surface"
// see https://github.com/westnordost/StreetComplete/pull/327#discussion_r121937808
private val ROADS_WITH_SURFACES_BROADLY_DEFINED = arrayOf(
"trunk","trunk_link","motorway","motorway_link",
"primary", "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link",
"unclassified", "residential", "living_street", "pedestrian", "track", "road",
"service"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package de.westnordost.streetcomplete.quests.surface

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.quests.AGroupedImageListQuestAnswerFragment
import de.westnordost.streetcomplete.quests.OtherAnswer
import de.westnordost.streetcomplete.util.TextChangedWatcher
import de.westnordost.streetcomplete.view.Item

class DetailRoadSurfaceForm : AGroupedImageListQuestAnswerFragment<String, DetailSurfaceAnswer>() {

override val topItems get() =
if (osmElement!!.tags["surface"] == "paved")
listOf(Surface.ASPHALT, Surface.CONCRETE, Surface.SETT, Surface.PAVING_STONES, Surface.WOOD, Surface.GRASS_PAVER).toItems()
else
listOf(Surface.DIRT, Surface.GRASS, Surface.PEBBLES, Surface.FINE_GRAVEL, Surface.SAND, Surface.COMPACTED).toItems()

// note that for unspecific groups null is used as a value, it makes them unselecteable
override val allItems = listOf(
Item(null, R.drawable.panorama_surface_paved, R.string.quest_surface_value_paved, null, listOf(
Surface.ASPHALT, Surface.CONCRETE, Surface.PAVING_STONES,
Surface.SETT, Surface.UNHEWN_COBBLESTONE, Surface.GRASS_PAVER,
Surface.WOOD, Surface.METAL
).toItems()),
Item(null, R.drawable.panorama_surface_unpaved, R.string.quest_surface_value_unpaved, null, listOf(
Surface.COMPACTED, Surface.FINE_GRAVEL, Surface.GRAVEL,
Surface.PEBBLES
).toItems()),
Item(null, R.drawable.panorama_surface_ground, R.string.quest_surface_value_ground, null, listOf(
Surface.DIRT, Surface.GRASS, Surface.SAND
).toItems())
)

private var isInExplanationMode = false;
private var explanationInput: EditText? = null

override val otherAnswers = listOf(
OtherAnswer(R.string.ic_quest_surface_detailed_answer_impossible) { confirmSwitchToNoDetailedTagPossible() }
)

private fun setLayout(layoutResourceId: Int) {
val view = setContentView(layoutResourceId)

val onChanged = TextChangedWatcher {
checkIsFormComplete()
}
explanationInput = view.findViewById(R.id.explanationInput)
explanationInput?.addTextChangedListener(onChanged)
}

private val explanation: String get() = explanationInput?.text?.toString().orEmpty().trim()

override fun isFormComplete(): Boolean {
if(isInExplanationMode) {
return explanation.isNotEmpty()
} else {
return super.isFormComplete()
}
}

override fun onClickOk() {
if(isInExplanationMode) {
applyAnswer(DetailingImpossibleAnswer(explanation))
} else {
super.onClickOk();
}
}

override fun onClickOk(value: String) {
// must not happen in isInExplanationMode
applyAnswer(SurfaceAnswer(value))
}

private fun confirmSwitchToNoDetailedTagPossible() {
AlertDialog.Builder(activity!!)
.setMessage(R.string.ic_quest_surface_detailed_answer_impossible_confirmation)
.setPositiveButton(R.string.quest_generic_confirmation_yes) {
_, _ -> switchToExplanationLayout()
}
.setNegativeButton(R.string.quest_generic_cancel, null)
.show()

}

private fun switchToExplanationLayout(){
isInExplanationMode = true
setLayout(R.layout.quest_surface_detailed_answer_impossible)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)

isInExplanationMode = savedInstanceState?.getBoolean(IS_IN_EXPLANATION_MODE) ?: false
setLayout(if (isInExplanationMode) R.layout.quest_surface_detailed_answer_impossible else R.layout.quest_generic_list)

return view
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(IS_IN_EXPLANATION_MODE, isInExplanationMode)
}

companion object {
private const val IS_IN_EXPLANATION_MODE = "is_in_explanation_mode"
}

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

sealed class DetailSurfaceAnswer(open val value: String)

data class DetailingImpossibleAnswer(override val value: String) : DetailSurfaceAnswer(value)
data class SurfaceAnswer(override val value: String) : DetailSurfaceAnswer(value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/space_8dp"
android:showDividers="middle">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ic_quest_surface_detailed_answer_impossible_description"/>

<EditText
android:id="@+id/explanationInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:gravity="top|start"
android:minLines="3"
tools:text="Surface is a mosaic of paving stones, wood and metal."
android:maxLength="255"
android:scrollHorizontally="false"
android:autofillHints="Tap here to write."
/>
</LinearLayout>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -850,4 +850,8 @@ Otherwise, you can download another keyboard in the app store. Popular keyboards
<string name="user_profile_days_active">Days\nactive</string>
<string name="user_profile_achievement_levels">Achievement\nlevels</string>

<string name="ic_quest_surface_detailed_answer_impossible">Multiple surfaces at once…</string>
<string name="ic_quest_surface_detailed_answer_impossible_confirmation">Are you sure that it is impossible to specify surface? Note \"Differs along the way\" option allowing to handle different surface sections. Please use \"Can\'t say\" if there is a single surface but it is not available as an answer.</string>
<string name="ic_quest_surface_detailed_answer_impossible_description">Why specifying single surface is impossible? Text length is limited to 255 characters.</string>
<string name="quest_generic_cancel">Cancel</string>
</resources>

0 comments on commit 5c47912

Please sign in to comment.