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

street furniture overlay #5373

Merged
merged 48 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8854bc5
street furniture overlay
matkoniecz Oct 12, 2023
cc83416
add hiunting stand
matkoniecz Nov 14, 2023
c8a486f
remove unused method
westnordost Nov 14, 2023
d3f3861
fix typo
matkoniecz Nov 15, 2023
28a65a4
refactoring popular shops
matkoniecz Nov 15, 2023
bdca6cc
add amenity=bicycle_wash
matkoniecz Nov 15, 2023
721e642
lifecycle prefix handler
matkoniecz Nov 15, 2023
9e01dd1
try to reconstruct from lifecycle prefix
matkoniecz Nov 15, 2023
e90acba
simplify syntax
matkoniecz Nov 15, 2023
d3149f6
fix syntax error
matkoniecz Nov 15, 2023
c217e9d
add values from systematic taginfo review
matkoniecz Nov 19, 2023
cbfa267
better marker
matkoniecz Nov 19, 2023
c7a289c
group strings
matkoniecz Nov 19, 2023
aa0014f
support table_soccer tables
matkoniecz Nov 23, 2023
53ae3ae
move unit test
westnordost Nov 23, 2023
5de8a9f
use bench as overlay icon
westnordost Nov 23, 2023
9129808
remove unnecessary suspend function
westnordost Nov 23, 2023
181b578
Merge branch 'master' into street_furniture
westnordost Nov 23, 2023
228bfd1
use "plus" icon for adding a street furniture poi
westnordost Nov 23, 2023
79a3c7c
Features without a dedicated icon shall fall back to a generic marker…
westnordost Nov 23, 2023
ef3ff23
remove duplicate
westnordost Nov 23, 2023
99b3c6a
add comments about the number of usages
westnordost Nov 23, 2023
1f6e108
Merge remote-tracking branch 'upstream/master' into street_furniture
matkoniecz Nov 27, 2023
c17951a
fix broken prefix
matkoniecz Nov 27, 2023
165a784
fix map display
matkoniecz Dec 5, 2023
49e4c47
better handle objects not in presets
matkoniecz Dec 5, 2023
8d636db
Merge remote-tracking branch 'upstream/master' into street_furniture
matkoniecz Dec 5, 2023
ca95588
man mades together
matkoniecz Dec 5, 2023
4243ef2
show also natural=spring as other water source features are shown
matkoniecz Dec 5, 2023
1e94454
Merge branch 'master' into street_furniture
matkoniecz Feb 4, 2024
dc238ff
fix merge
westnordost Feb 4, 2024
15e2dd9
refactor handling of disused
westnordost Feb 5, 2024
917d05c
sort features alphabetically (consistent with Shop.kt)
westnordost Feb 5, 2024
2db50bf
for added features, the geometry type is always POINT
westnordost Feb 7, 2024
6eb6ddf
Extend "shops" category to "places" category (fixes #5152)
westnordost Feb 7, 2024
ab4aed4
add many street furniture POIs
westnordost Feb 7, 2024
29db166
rename Shop to Place
westnordost Feb 7, 2024
3d547fe
review list of quick-select features
westnordost Feb 7, 2024
06fcff1
reorder overlays so that street furniture is next to shops
westnordost Feb 7, 2024
566d0a5
add delete node answer
westnordost Feb 13, 2024
732cc93
include a few more things
westnordost Feb 13, 2024
46dbb6f
do not allow editing features
westnordost Feb 14, 2024
46909c0
add playground equipment
westnordost Feb 14, 2024
9ec2e55
use dot icon
westnordost Feb 14, 2024
693307e
rename StreetFurniture to "Thing"
westnordost Feb 14, 2024
6521238
rename StreetFurniture to "Thing"
westnordost Feb 14, 2024
13bf845
simplify edit creation (because modification of feature is disabled)
westnordost Feb 14, 2024
f9c956a
remove unused imports
westnordost Feb 14, 2024
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
westnordost marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package de.westnordost.streetcomplete.osm

import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression

fun isStreetFurnitureFragment(prefix: String? = null): String {
val amenities = listOf(
"bicycle_parking", "bicycle_rental", "bench", "longer", "bbq", "grit_bin", "toilets",
westnordost marked this conversation as resolved.
Show resolved Hide resolved
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
"public_bookcase", "give_box", "clock", "bicycle_repair_station", "charging_station",
"parcel_locker", "telephone", "drinking_water", "vending_machine",
"atm", "waste_basket", "trolley_bay", "hunting_stand",
// "post_box", "letter_box", - blocked by https://github.com/streetcomplete/StreetComplete/issues/4916
// waiting for response in https://github.com/ideditor/schema-builder/issues/94
// man_made = street_cabinet and street_cabinet = postal_service
// is also disabled to avoid bad data being added
)
val p = if (prefix != null) "$prefix:" else ""
return ("""(
${p}amenity ~ ${amenities.joinToString("|")}
or (${p}amenity = recycling and recycling_type = container)
or ${p}leisure ~ picnic_table|firepit
or ${p}man_made ~ water_tap|obelisk|cross|monitoring_station|flagpole|carpet_hanger|planter
or ${p}tourism ~ viewpoint|artwork
or (${p}tourism ~ information and information ~ guidepost|board|map|terminal)
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
or ${p}historic ~ memorial|monument|wayside_shrine|wayside_cross|boundary_stone
or ${p}highway ~ milestone|street_lamp|emergency_access_point
or ${p}emergency ~ fire_hydrant|life_ring|phone|defibrillator|siren|lifeguard|assembly_point|access_point
or ${p}advertising
or ${p}leisure = pitch and sport ~ table_tennis|sport=chess
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
or ${p}natural ~ tree
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
or ${p}man_made = street_cabinet and street_cabinet != postal_service
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
)""")
}
val IS_DISUSED_STREET_FURNITURE_EXPRESSION = """
nodes, ways, relations with
${isStreetFurnitureFragment("disused")}
""".toElementFilterExpression()

val IS_STREET_FURNITURE_INCLUDING_DISUSED_EXPRESSION = """
nodes, ways, relations with
${isStreetFurnitureFragment()}
or ${isStreetFurnitureFragment("disused")}
""".toElementFilterExpression()
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import de.westnordost.streetcomplete.overlays.address.AddressOverlay
import de.westnordost.streetcomplete.overlays.cycleway.CyclewayOverlay
import de.westnordost.streetcomplete.overlays.shops.ShopsOverlay
import de.westnordost.streetcomplete.overlays.sidewalk.SidewalkOverlay
import de.westnordost.streetcomplete.overlays.street_furniture.StreetFurnitureOverlay
import de.westnordost.streetcomplete.overlays.street_parking.StreetParkingOverlay
import de.westnordost.streetcomplete.overlays.surface.SurfaceOverlay
import de.westnordost.streetcomplete.overlays.way_lit.WayLitOverlay
Expand Down Expand Up @@ -56,4 +57,5 @@ fun overlaysRegistry(
2 to StreetParkingOverlay(),
3 to AddressOverlay(getCountryCodeByLocation),
4 to ShopsOverlay(getFeature),
7 to StreetFurnitureOverlay(getFeature),
))
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import de.westnordost.streetcomplete.osm.replaceShop
import de.westnordost.streetcomplete.overlays.AbstractOverlayForm
import de.westnordost.streetcomplete.overlays.AnswerItem
import de.westnordost.streetcomplete.quests.LocalizedNameAdapter
import de.westnordost.streetcomplete.quests.shop_type.topFeatureCodesOfPopularShoplikePOIs
import de.westnordost.streetcomplete.util.getLocalesForFeatureDictionary
import de.westnordost.streetcomplete.util.getLocationLabel
import de.westnordost.streetcomplete.util.ktx.geometryType
Expand Down Expand Up @@ -109,7 +110,8 @@ class ShopsOverlayForm : AbstractOverlayForm() {
countryOrSubdivisionCode,
featureCtrl.feature?.name,
::filterOnlyShops,
::onSelectedFeature
::onSelectedFeature,
topFeatureCodesOfPopularShoplikePOIs(),
).show()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package de.westnordost.streetcomplete.overlays.street_furniture

import de.westnordost.osmfeatures.Feature
import de.westnordost.osmfeatures.GeometryType
import java.util.Locale

data class DummyFeature(
private val id: String,
private val name: String,
private val icon: String,
private val addTags: Map<String, String>
) : Feature {
override fun getId() = id
override fun getTags() = addTags
override fun getGeometry() = listOf(GeometryType.POINT, GeometryType.AREA)
override fun getName() = name
override fun getIcon() = icon
override fun getImageURL() = null
override fun getNames() = listOf(name)
override fun getTerms() = emptyList<String>()
override fun getIncludeCountryCodes() = emptyList<String>()
override fun getExcludeCountryCodes() = emptyList<String>()
override fun isSearchable() = false
override fun getMatchScore() = 1.0
override fun getAddTags() = addTags
override fun getRemoveTags() = emptyMap<String, String>()
override fun getCanonicalNames() = emptyList<String>()
override fun getCanonicalTerms() = emptyList<String>()
override fun isSuggestion() = false
override fun getLocale(): Locale = Locale.getDefault()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package de.westnordost.streetcomplete.overlays.street_furniture

import androidx.core.content.ContentProviderCompat.requireContext
import de.westnordost.osmfeatures.Feature
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.osm.mapdata.filter
import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement
import de.westnordost.streetcomplete.osm.IS_DISUSED_STREET_FURNITURE_EXPRESSION
import de.westnordost.streetcomplete.osm.IS_STREET_FURNITURE_INCLUDING_DISUSED_EXPRESSION
import de.westnordost.streetcomplete.overlays.Color
import de.westnordost.streetcomplete.overlays.Overlay
import de.westnordost.streetcomplete.overlays.PointStyle
import de.westnordost.streetcomplete.overlays.PolygonStyle
import de.westnordost.streetcomplete.util.getNameLabel

class StreetFurnitureOverlay(private val getFeature: (tags: Map<String, String>) -> Feature?) : Overlay {

override val title = R.string.overlay_street_furniture
override val icon = R.drawable.ic_quest_apple // TODO
westnordost marked this conversation as resolved.
Show resolved Hide resolved
override val changesetComment = "Survey street furniture and similar objects"
override val wikiLink: String = "Street furniture"
override val achievements = listOf(EditTypeAchievement.CITIZEN)
override val isCreateNodeEnabled = true

override val sceneUpdates = listOf(
"layers.buildings.draw.buildings-style.extrude" to "false",
"layers.buildings.draw.buildings-outline-style.extrude" to "false"
)

override fun getStyledElements(mapData: MapDataWithGeometry) =
mapData
.filter(IS_STREET_FURNITURE_INCLUDING_DISUSED_EXPRESSION)
.mapNotNull { element ->
val feature = getFeature(element.tags)
val iconIdentifier = feature?.icon
if ( iconIdentifier == null) {
if (IS_DISUSED_STREET_FURNITURE_EXPRESSION.matches(element)) {
val icon = "ic_preset_maki_marker_stroked"
val label = null
val style = if (element is Node) {
PointStyle(icon, label)
} else {
PolygonStyle(Color.INVISIBLE, icon, label)
}
element to style
} else {
null
}
} else {
val icon = "ic_preset_" + iconIdentifier.replace('-', '_')
val label = getNameLabel(element.tags)

val style = if (element is Node) {
PointStyle(icon, label)
} else {
PolygonStyle(Color.INVISIBLE, icon, label)
}
element to style
}
}

override fun createForm(element: Element?) = StreetFurnitureOverlayForm()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package de.westnordost.streetcomplete.overlays.street_furniture

import android.os.Bundle
import android.view.View
import androidx.core.content.ContentProviderCompat
import de.westnordost.osmfeatures.Feature
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
import de.westnordost.streetcomplete.data.osm.edits.create.CreateNodeAction
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChangesBuilder
import de.westnordost.streetcomplete.data.osm.edits.update_tags.UpdateElementTagsAction
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.databinding.FragmentOverlayStreetFurnitureBinding
import de.westnordost.streetcomplete.osm.IS_DISUSED_STREET_FURNITURE_EXPRESSION
import de.westnordost.streetcomplete.osm.IS_STREET_FURNITURE_INCLUDING_DISUSED_EXPRESSION
import de.westnordost.streetcomplete.overlays.AbstractOverlayForm
import de.westnordost.streetcomplete.util.getLocalesForFeatureDictionary
import de.westnordost.streetcomplete.util.getLocationLabel
import de.westnordost.streetcomplete.util.ktx.geometryType
import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope
import de.westnordost.streetcomplete.view.controller.FeatureViewController
import de.westnordost.streetcomplete.view.dialogs.SearchFeaturesDialog
import kotlinx.coroutines.launch

class StreetFurnitureOverlayForm : AbstractOverlayForm() {

override val contentLayoutResId = R.layout.fragment_overlay_street_furniture
private val binding by contentViewBinding(FragmentOverlayStreetFurnitureBinding::bind)

private var originalFeature: Feature? = null

private lateinit var featureCtrl: FeatureViewController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val element = element
originalFeature = element?.let {
if (IS_DISUSED_STREET_FURNITURE_EXPRESSION.matches(element)) {
DummyFeature(
"street_furniture/unknown_disused",
requireContext().getString(R.string.unknown_disused_street_furniture),
"ic_preset_maki_marker_stroked",
element.tags
)
westnordost marked this conversation as resolved.
Show resolved Hide resolved
} else {
featureDictionary
.byTags(element.tags)
.forLocale(*getLocalesForFeatureDictionary(resources.configuration))
.forGeometry(element.geometryType)
.inCountry(countryOrSubdivisionCode)
.find()
.firstOrNull()
// if not found anything in the iD presets, then something weird happened
?: DummyFeature(
"street_furniture/unknown",
requireContext().getString(R.string.unknown_street_furniture),
"ic_preset_maki_marker_stroked",
element.tags
)
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// title hint label with name is a duplication, it is displayed in the UI already
setTitleHintLabel(element?.tags?.let { getLocationLabel(it, resources) })
setMarkerIcon(R.drawable.ic_quest_apple)
westnordost marked this conversation as resolved.
Show resolved Hide resolved

featureCtrl = FeatureViewController(featureDictionary, binding.featureTextView, binding.featureIconView)
featureCtrl.countryOrSubdivisionCode = countryOrSubdivisionCode
featureCtrl.feature = originalFeature

binding.featureView.setOnClickListener {
SearchFeaturesDialog(
requireContext(),
featureDictionary,
element?.geometryType,
countryOrSubdivisionCode,
featureCtrl.feature?.name,
::filterOnlyStreetFurniture,
::onSelectedFeature,
listOf(
// ordered by popularity, skipping trees as there are multiple variants of them
westnordost marked this conversation as resolved.
Show resolved Hide resolved
"highway/street_lamp",
"amenity/bench",
"emergency/fire_hydrant",
"amenity/bicycle_parking",
"amenity/shelter",
"amenity/toilets",
// "amenity/post_box",
// blocked by https://github.com/streetcomplete/StreetComplete/issues/4916
// waiting for response in https://github.com/ideditor/schema-builder/issues/94
"amenity/drinking_water",
"leisure/picnic_table",

// popular, a bit less than some competing entries
// but interesting and worth promoting
"emergency/defibrillator",
)
).show()
}
}

private fun filterOnlyStreetFurniture(feature: Feature): Boolean {
val fakeElement = Node(-1L, LatLon(0.0, 0.0), feature.tags, 0)
return IS_STREET_FURNITURE_INCLUDING_DISUSED_EXPRESSION.matches(fakeElement)
}

private fun onSelectedFeature(feature: Feature) {
featureCtrl.feature = feature
checkIsFormComplete()
}

override fun hasChanges(): Boolean =
originalFeature != featureCtrl.feature

override fun isFormComplete(): Boolean = featureCtrl.feature != null

override fun onClickOk() {
viewLifecycleScope.launch {
westnordost marked this conversation as resolved.
Show resolved Hide resolved
applyEdit(createEditAction(
element, geometry,
featureCtrl.feature!!, originalFeature,
))
}
}
}

private suspend fun createEditAction(
element: Element?,
geometry: ElementGeometry,
newFeature: Feature,
previousFeature: Feature?
): ElementEditAction {
val tagChanges = StringMapChangesBuilder(element?.tags ?: emptyMap())

for ((key, value) in previousFeature?.removeTags.orEmpty()) {
tagChanges.remove(key)
}
for ((key, value) in newFeature.addTags) {
tagChanges[key] = value
}

return if (element != null) {
UpdateElementTagsAction(element, tagChanges.create())
} else {
CreateNodeAction(geometry.center, tagChanges)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ShopGoneDialog(
featureCtrl.feature?.name,
::filterOnlyShops,
::onSelectedFeature,
topFeatureCodesOfPopularShoplikePOIs(),
true
).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class ShopTypeForm : AbstractOsmQuestForm<ShopTypeAnswer>() {
countryOrSubdivisionCode,
featureCtrl.feature?.name,
::filterOnlyShops,
::onSelectedFeature
::onSelectedFeature,
topFeatureCodesOfPopularShoplikePOIs(),
).show()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.westnordost.streetcomplete.quests.shop_type
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved

fun topFeatureCodesOfPopularShoplikePOIs(): List<String> {
// ordered by usage number according to taginfo
return listOf(
matkoniecz marked this conversation as resolved.
Show resolved Hide resolved
"amenity/restaurant",
"shop/convenience",
"amenity/cafe",
"shop/supermarket",
"amenity/fast_food",
"amenity/pharmacy",
"shop/clothes",
"shop/hairdresser"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class SearchFeaturesDialog(
text: String? = null,
private val filterFn: (Feature) -> Boolean = { true },
private val onSelectedFeatureFn: (Feature) -> Unit,
private val codesOfDefaultFeatures: List<String>,
private val dismissKeyboardOnClose: Boolean = false,
) : AlertDialog(context) {

Expand All @@ -39,17 +40,7 @@ class SearchFeaturesDialog(
private val searchText: String? get() = binding.searchEditText.nonBlankTextOrNull

private val defaultFeatures: List<Feature> by lazy {
listOf(
// ordered by usage number according to taginfo
"amenity/restaurant",
"shop/convenience",
"amenity/cafe",
"shop/supermarket",
"amenity/fast_food",
"amenity/pharmacy",
"shop/clothes",
"shop/hairdresser"
).mapNotNull {
codesOfDefaultFeatures.mapNotNull {
featureDictionary
.byId(it)
.forLocale(*locales)
Expand Down
Loading