Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
- fix on rare occasions, a solved quest would immediately reappear (#5545), thanks @Helium314
- Road surfaces: fix quest immediately reappeared when answer conflicted with the recorded information for the track visibility (#6116)
- Opening hours: fix don't consider a rare but valid syntax for opening hours as invalid (#6125)
- fix escalators were previously labeled as steps (#5728)

### Improvements

- Hairdresser customers: Don't ask for barber shops (#6108)
- Bus stop names: Suggest names of nearby bus stops (e.g. the one from the other side of the road) (#6067, #5187) by @kmpoppe
- Baby changing table: Also ask when information about toilets isn't recorded yet (#6115), by @agent-redd
- small improvements on the places overlay (#6100, #5985, #6086)
- Places overlay: Display correctly and allow to select some generic places (some office, some club, some healthcare facility, some shop) (#6140)
- small improvements on the places overlay (#6100, #5985, #6086, #6085, #6140)

## v60.1

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ dependencies {
// finding in which country we are for country-specific logic
implementation("de.westnordost:countryboundaries:2.1")
// finding a name for a feature without a name tag
implementation("de.westnordost:osmfeatures:6.3")
implementation("de.westnordost:osmfeatures:7.0")

// widgets
implementation("androidx.viewpager2:viewpager2:1.1.0")
Expand Down
26 changes: 0 additions & 26 deletions app/src/main/assets/osmfeatures/default/presets.json
Original file line number Diff line number Diff line change
Expand Up @@ -8834,32 +8834,6 @@
"fire_hydrant:type": "pillar"
}
},
"disused/shop": {
"icon": "fas-store-alt-slash",
"geometry": ["point", "area"],
"tags": {
"disused:shop": "*"
},
"matchScore": 0.05,
"searchable": false
},
"disused/railway": {
"icon": "temaki-rail_profile",
"geometry": ["point", "vertex", "line", "area"],
"tags": {
"disused:railway": "*"
},
"matchScore": 0.05,
"searchable": false
},
"disused/amenity": {
"geometry": ["point", "vertex", "area"],
"tags": {
"disused:amenity": "*"
},
"matchScore": 0.05,
"searchable": false
},
"disc_golf/tee": {
"icon": "temaki-disc_golf_basket",
"geometry": ["point", "vertex"],
Expand Down
50 changes: 50 additions & 0 deletions app/src/main/java/de/westnordost/streetcomplete/osm/Feature.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package de.westnordost.streetcomplete.osm

import de.westnordost.osmfeatures.Feature
import de.westnordost.osmfeatures.GeometryType
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.data.osm.mapdata.Relation
import de.westnordost.streetcomplete.data.osm.mapdata.Way

/** Apply this feature to the given [tags], optionally removing a [previousFeature] first, i.e.
* replacing it. */
fun Feature.applyTo(tags: Tags, previousFeature: Feature? = null) {
if (previousFeature != null) {
for ((key, value) in previousFeature.removeTags) {
if (tags[key] == value) tags.remove(key)
}
for (key in previousFeature.removeTagKeys) {
tags.remove(key)
}
}
for ((key, value) in addTagKeys.associateWith { "yes" } + addTags) {
if (key !in tags || preserveTags.none { it.containsMatchIn(key) }) {
tags[key] = value
}
}
}

/** Return an exemplary element that would match this feature. */
fun Feature.toElement(): Element {
val allTags = tagKeys.associateWith { "yes" } + tags
return when {
GeometryType.POINT in geometry ||
GeometryType.VERTEX in geometry -> {
Node(-1L, NULL_ISLAND, allTags)
}
GeometryType.LINE in geometry || GeometryType.AREA in geometry -> {
Way(-1L, NULL_ISLAND_NODES, allTags)
}
GeometryType.RELATION in geometry -> {
Relation(-1L, listOf(), allTags)
}
else -> {
Node(-1L, NULL_ISLAND, allTags)
}
}
}

private val NULL_ISLAND = LatLon(0.0, 0.0)
private val NULL_ISLAND_NODES = listOf(-1L, -2L, -3L, -1L)
24 changes: 24 additions & 0 deletions app/src/main/java/de/westnordost/streetcomplete/osm/Lifecycle.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.westnordost.streetcomplete.osm

import de.westnordost.osmfeatures.BaseFeature
import de.westnordost.osmfeatures.Feature
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.util.ktx.copy

Expand All @@ -19,3 +21,25 @@ private fun Map<String, String>.hasPrefixed(prefix: String): Boolean =
private fun Map<String, String>.getPrefixedOnly(prefix: String): Map<String, String> = this
.filter { it.key.startsWith("$prefix:") }
.mapKeys { it.key.substring(prefix.length + 1) }

/** Returns a copy of this feature with all its tags prefixed with the given lifecycle [prefix]. */
fun Feature.toPrefixedFeature(prefix: String, label: String = prefix): Feature = BaseFeature(
id = "$id/$prefix",
names = names.map { "$name ($label)" },
icon = icon,
imageURL = imageURL,
geometry = geometry,
terms = listOf(),
includeCountryCodes = listOf(),
excludeCountryCodes = listOf(),
tags = tags.mapKeys { "$prefix:${it.key}" },
addTags = addTags.mapKeys { "$prefix:${it.key}" },
removeTags = removeTags.mapKeys { "$prefix:${it.key}" },
tagKeys = tagKeys.mapTo(HashSet()) { "$prefix:$it" },
addTagKeys = addTagKeys.mapTo(HashSet()) { "$prefix:$it" },
removeTagKeys = removeTagKeys.mapTo(HashSet()) { "$prefix:$it" },
preserveTags = listOf(),
isSuggestion = isSuggestion,
isSearchable = false,
matchScore = matchScore,
)
37 changes: 8 additions & 29 deletions app/src/main/java/de/westnordost/streetcomplete/osm/Place.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.westnordost.streetcomplete.osm

import de.westnordost.osmfeatures.Feature
import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression
import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChangesBuilder
import de.westnordost.streetcomplete.data.osm.mapdata.Element

/** Return whether this element is a kind of place, regardless whether it is currently vacant or
Expand Down Expand Up @@ -202,25 +202,6 @@ private val IS_PLACE_EXPRESSION by lazy {
""".toElementFilterExpression()
}

/** Get tags to denote the element with the given [tags] as disused */
fun getDisusedPlaceTags(tags: Map<String, String>?): Map<String, String> {
val (key, value) = tags?.entries?.find { it.key in placeTypeKeys }?.toPair() ?: ("shop" to "yes")
return mapOf("disused:$key" to value)
}

private val placeTypeKeys = setOf(
"amenity",
"club",
"craft",
"emergency",
"healthcare",
"leisure",
"office",
"military",
"shop",
"tourism"
)

/** Expression to see if an element is some kind of vacant shop */
private val IS_VACANT_PLACE_EXPRESSION = """
nodes, ways, relations with
Expand All @@ -243,20 +224,18 @@ val POPULAR_PLACE_FEATURE_IDS = listOf(
"amenity/pharmacy", // 0.3 M
)

/** Replace a place with the given new tags.
* Removes any place-related tags before adding the given [tags]. */
fun StringMapChangesBuilder.replacePlace(tags: Map<String, String>) {
removeCheckDates()
/** Apply replacing a place feature to the given [tags]
* Removes any place-related tags before applying this feature to the given [tags]. */
fun Feature.applyReplacePlaceTo(tags: Tags) {
tags.removeCheckDates()

for (key in keys) {
for (key in tags.keys.toList()) {
if (KEYS_THAT_SHOULD_BE_REMOVED_WHEN_PLACE_IS_REPLACED.any { it.matches(key) }) {
remove(key)
tags.remove(key)
}
}

for ((key, value) in tags) {
this[key] = value
}
applyTo(tags)
}

// generated by "make update" from https://github.com/mnalis/StreetComplete-taginfo-categorize/
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/de/westnordost/streetcomplete/osm/Things.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package de.westnordost.streetcomplete.osm
import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression
import de.westnordost.streetcomplete.data.osm.mapdata.Element

/** Return whether this element is a kind of thing, regardless whether it is disused or not */
fun Element.isThingOrDisusedThing(): Boolean =
isThing() || isDisusedThing()

fun Element.isThing(): Boolean =
IS_THING_EXPRESSION.matches(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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.isDisusedPlace
import de.westnordost.streetcomplete.osm.isPlaceOrDisusedPlace
import de.westnordost.streetcomplete.overlays.Color
import de.westnordost.streetcomplete.overlays.Overlay
Expand Down Expand Up @@ -37,9 +38,11 @@ class PlacesOverlay(private val getFeature: (Element) -> Feature?) : Overlay {
.asSequence()
.filter { it.isPlaceOrDisusedPlace() }
.map { element ->
val feature = getFeature(element)
// show disused places always with the icon for "disused shop" icon
val icon = getFeature(element)?.icon?.let { presetIconIndex[it] }
?: if (element.isDisusedPlace()) R.drawable.ic_preset_fas_store_alt_slash else null
?: R.drawable.ic_preset_maki_shop

val icon = feature?.icon?.let { presetIconIndex[it] } ?: R.drawable.ic_preset_maki_shop
val label = getNameLabel(element.tags)

val style = if (element is Node) {
Expand All @@ -59,5 +62,6 @@ class PlacesOverlay(private val getFeature: (Element) -> Feature?) : Overlay {
.map { it to PointStyle(icon = null, label = "◽") }

override fun createForm(element: Element?) =
// this check is necessary because the form shall not be shown for entrances
if (element == null || element.isPlaceOrDisusedPlace()) PlacesOverlayForm() else null
}
Loading