diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmElementQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmElementQuestType.kt index d5627ef88e..cd1e347fdf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmElementQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmElementQuestType.kt @@ -54,7 +54,7 @@ interface OsmElementQuestType : QuestType, ElementEditType { * elements that are expected to be some kind of shop/amenity should be replaceable this way, * i.e. anything that when it's gone, there is a vacant shop then. * */ - val isReplaceShopEnabled: Boolean get() = false + val isReplacePlaceEnabled: Boolean get() = false override val title: Int get() = getTitle(emptyMap()) diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/Lifecycle.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/Lifecycle.kt new file mode 100644 index 0000000000..8d938a182d --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/Lifecycle.kt @@ -0,0 +1,21 @@ +package de.westnordost.streetcomplete.osm + +import de.westnordost.streetcomplete.data.osm.mapdata.Element +import de.westnordost.streetcomplete.util.ktx.copy + +/** Returns a copy of this element with all tags prefixed with the given [lifecycle] prefix moved + * to normal tags space and all others cleared. + * Returns `null` if the element has no tags with the given [lifecycle] prefix + * + * E.g. when calling this method with [lifecycle] = "disused" for an element with the tags + * `building=yes` + `disused:shop=yes` , a copy of that element is returned with the only tag + * `shop=yes`. */ +fun Element.asIfItWasnt(lifecycle: String): Element? = + if (tags.hasPrefixed(lifecycle)) copy(tags = tags.getPrefixedOnly(lifecycle)) else null + +private fun Map.hasPrefixed(prefix: String): Boolean = + any { it.key.startsWith("$prefix:") } + +private fun Map.getPrefixedOnly(prefix: String): Map = this + .filter { it.key.startsWith("$prefix:") } + .mapKeys { it.key.substring(prefix.length + 1) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/Shop.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/Place.kt similarity index 61% rename from app/src/main/java/de/westnordost/streetcomplete/osm/Shop.kt rename to app/src/main/java/de/westnordost/streetcomplete/osm/Place.kt index 6553cdbb02..1d725bd991 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/Shop.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/Place.kt @@ -2,9 +2,247 @@ package de.westnordost.streetcomplete.osm 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 + * not */ +fun Element.isPlaceOrDisusedShop(): Boolean = + isPlace() || isDisusedPlace() + +/** Return whether this element is a kind of disused or vacant place */ +fun Element.isDisusedPlace(): Boolean = + IS_VACANT_PLACE_EXPRESSION.matches(this) || + this.asIfItWasnt("disused")?.let { IS_PLACE_EXPRESSION.matches(it) } == true + +fun Element.isPlace(): Boolean = + IS_PLACE_EXPRESSION.matches(this) + +/** Map features like shops or amenities that usually have a name and can be entered. + * + * To demarcate this category from others, the following are not included: + * - things that are rather structures or buildings than places (e.g. communication towers, train station) + * - landuses, outside-things (golf courses, zoos, marketplaces, car parking) + * + * Note: When this function is modified, please follow update instructions in: + * https://github.com/mnalis/StreetComplete-taginfo-categorize/blob/master/README.md + * + * Important: The contribution policy here is that only things may be added if an iD preset for it + * exists. This policy exists in order to reduce effort to maintain this list, i.e. we + * don't want to check, weigh and balance requests in parallel to iD maintainers (in + * terms of notability, it being unambiguous, consensus etc.) + * */ +private val IS_PLACE_EXPRESSION by lazy { + val tags = mapOf( + "amenity" to listOf( + /* grouped by subcategory, sorted by alphabet */ + + /* sustenance */ + "bar", + "biergarten", + "cafe", + "fast_food", + "food_court", + "ice_cream", + "pub", + "restaurant", + + /* education */ + "childcare", + "college", + "dancing_school", + "dive_centre", + "dojo", + "driving_school", + "kindergarten", + "language_school", + "library", + "music_school", + "prep_school", + "research_institute", + "school", + "toy_library", + "training", + "university", + + /* transportation */ + "boat_rental", // usually there is some kind of office, even if it is just a small stall + "car_rental", // usually there is some kind of office + "car_wash", + "fuel", + "motorcycle_rental", + "vehicle_inspection", + + /* financial */ + "bank", + "bureau_de_change", + "mobile_money_agent", + "money_transfer", + "payment_centre", + + /* healthcare */ + "clinic", + "dentist", + "doctors", + "health_post", + "hospital", + "nursing_home", + "pharmacy", + "social_facility", + "veterinary", + + /* entertainment, arts & culture */ + "archive", + "arts_centre", + "brothel", + "casino", + "cinema", + "community_centre", + "conference_centre", + "events_venue", + "exhibition_centre", + "gambling", + "hookah_lounge", + "love_hotel", + "music_venue", + "nightclub", + "planetarium", + "ski_rental", + "social_centre", + "stripclub", + "studio", + "swingerclub", + "theatre", + + /* public service */ + "courthouse", + "embassy", // deprecated now + "fire_station", + "mailroom", + "police", + "post_depot", + "post_office", + "prison", + "ranger_station", + "townhall", + + /* facilities */ + "lavoir", + "left_luggage", + + /* others */ + "animal_boarding", + "animal_shelter", + "animal_training", + "coworking_space", // basically an office + "crematorium", + "funeral_hall", + "internet_cafe", + "meditation_centre", + "monastery", + "mortuary", + "place_of_mourning", + "place_of_worship", + "public_bath", + ), + "emergency" to listOf( + "air_rescue_service", + "ambulance_station", + "disaster_response", + "mountain_rescue", + "water_rescue", + ), + "leisure" to listOf( + "adult_gaming_centre", + "amusement_arcade", + "bowling_alley", + "dance", + "escape_game", + "fitness_centre", + "hackerspace", + "ice_rink", + "indoor_play", + "sauna", + "sports_hall", + "stadium", + "tanning_salon", + "trampoline_park", + ), + "military" to listOf( + "office", + ), + "tourism" to listOf( + "alpine_hut", + "apartment", + "aquarium", + "chalet", + "gallery", + "guest_house", + "hostel", + "hotel", + "hunting_lodge", + // "information", only if it is an office, see below + "museum", + "motel", + "trail_riding_station", // motel for riders + "wilderness_hut" + ), + ) + .map { it.key + " ~ " + it.value.joinToString("|") } + .joinToString("\n or ") + + """ + nodes, ways, relations with + $tags + or club + or craft + or healthcare + or office and office !~ no|vacant + or shop and shop !~ no|vacant + or tourism = information and information ~ office|visitor_centre + """.toElementFilterExpression() +} + +/** Expression to see if an element is some kind of vacant shop */ +private val IS_VACANT_PLACE_EXPRESSION = """ + nodes, ways, relations with + shop = vacant + or office = vacant + or amenity = vacant +""".toElementFilterExpression() + +/** iD preset ids of popular place types */ +val POPULAR_PLACE_FEATURE_IDS = listOf( + // ordered roughly by usage number according to taginfo + "amenity/restaurant", // 1.4 M + "amenity/cafe", // 0.5 M + "amenity/fast_food", // 0.5 M + "shop/convenience", // 0.7 M + "shop/supermarket", // 0.5 M + "shop/clothes", // 0.4 M + "shop/hairdresser", // 0.3 M + "tourism/hotel", // 0.4 M + "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) { + removeCheckDates() + + for (key in keys) { + if (KEYS_THAT_SHOULD_BE_REMOVED_WHEN_PLACE_IS_REPLACED.any { it.matches(key) }) { + remove(key) + } + } + + for ((key, value) in tags) { + this[key] = value + } +} + // generated by "make update" from https://github.com/mnalis/StreetComplete-taginfo-categorize/ -val KEYS_THAT_SHOULD_BE_REMOVED_WHEN_SHOP_IS_REPLACED = listOf( +private val KEYS_THAT_SHOULD_BE_REMOVED_WHEN_PLACE_IS_REPLACED = listOf( "shop_?[1-9]?(:.*)?", "craft_?[1-9]?", "amenity_?[1-9]?", "old_amenity", "old_shop", "information", "leisure", "office_?[1-9]?", "tourism", // popular shop=* / craft=* subkeys @@ -95,205 +333,3 @@ val KEYS_THAT_SHOULD_BE_REMOVED_WHEN_SHOP_IS_REPLACED = listOf( "engineering", "forestry", "foundation", "lawyer", "logistics", "military", "community_centre", "bank", ).map { it.toRegex() } - -fun StringMapChangesBuilder.replaceShop(tags: Map) { - removeCheckDates() - - for (key in keys) { - if (KEYS_THAT_SHOULD_BE_REMOVED_WHEN_SHOP_IS_REPLACED.any { it.matches(key) }) { - remove(key) - } - } - - for ((key, value) in tags) { - this[key] = value - } -} - -/** Tenant of retail or commercial rooms, e.g. a shop, an office etc. - * Something that can occupy (a part) of a non-purpose-built building - * - * So, no larger purpose-built things like malls, cinemas, theatres, zoos, aquariums, - * bowling alleys... - * - * It is possible to specify a prefix for the keys here, e.g. "disused", to find disused shops etc. - * - * Note: When this function is modified, please follow update instructions in: - * https://github.com/mnalis/StreetComplete-taginfo-categorize/blob/master/README.md - * */ -fun isShopExpressionFragment(prefix: String? = null): String { - val p = if (prefix != null) "$prefix:" else "" - return ("""( - ${p}shop and ${p}shop !~ no|vacant|mall - or ${p}office and ${p}office !~ no|vacant - or ${p}healthcare and ${p}healthcare != hospital - or ${p}craft - or ${p}club - or ${p}tourism = information and ${p}information = office - or ${p}amenity = social_facility and ${p}social_facility ~ ${listOf( - // only non-residential ones - "ambulatory_care", - "clothing_bank", - "dairy_kitchen", - "day_care", - "food_bank", - "healthcare", - "outreach", - "soup_kitchen", - "workshop" - ).joinToString("|")} - or """ + mapOf( - "leisure" to listOf( - "adult_gaming_centre", - "amusement_arcade", - // "bowling_alley", // purpose-built - "dance", // not necessarily purpose-built, see fitness centre - "dancing_school", - "escape_game", - // "ice_rink", // purpose-built - "indoor_play", - "fitness_centre", // not necessarily purpose-built, esp. the smaller ones - "hackerspace", - // "resort", // a kind of hotel+theme/water/whatever park - "sauna", - // "sports_centre", // purpose-built - "tanning_salon", - // "trampoline_park", // see sports centre - // "water_park" // purpose-built - ), - "tourism" to listOf( - // tourism = information only if it is an office, see above - // purpose-built - // "aquarium", - // "zoo", - // "theme_park", - "gallery", // could be just an artist's show-room - "museum", // only the larger ones are purpose-built - // tourist accommodations are usually not in something that could otherwise be just a showroom, office etc. - // "alpine_hut", - // "apartment", - // "chalet", - // "camp_site", - // "guest_house", - // "hostel", - // "hotel", - // "motel", - - ), - "amenity" to listOf( - /* amenity, the "garbage patch in the OSM ocean" - https://media.ccc.de/v/sotm2022-18515-every-door-and-the-future-of-poi-in-openstreetmap#t=528 - sorted by occurrence on wiki page Key:amenity */ - /* sustenance */ - "bar", - "biergarten", - "cafe", - "fast_food", - "food_court", - "ice_cream", - "internet_cafe", - "pub", - "restaurant", - - /* education */ - "childcare", - // "college", purpose-built - "dive_centre", // depends, but can be in a showroom just like a driving school - "dojo", // same as fitness_centre - "driving_school", - "kindergarten", - "language_school", - "library", - "music_school", - "prep_school", - // "research_institute", purpose-built - // "school", purpose-built - "toy_library", - "training", - // "university", purpose-built - - /* transportation */ - // "bicycle_rental", // usually outside, could be automatic too - // "boat_rental", // usually outside, could be automatic too - "car_rental", - "car_wash", // purpose-built, but see fuel - "fuel", // purpose-built but too much of a shop that it would be weird to leave out - "motorcycle_rental", - "vehicle_inspection", // often similar to a car repair shop - - /* financial */ - "bank", - "bureau_de_change", - "money_transfer", - "payment_centre", - - /* healthcare */ - "clinic", // sizes vary a lot, not necessarily purpose-built - // "crematorium", // purpose-built - "dentist", - "doctors", - // "mortuary", // purpose-built - // "hospital", // purpose-built - "pharmacy", - // "social_facility", only if it is not residential, see above - "veterinary", - - /* entertainment, arts & culture */ - "arts_centre", - "brothel", - "casino", // usually purpose-built, but doesn't have to be - // "cinema", // typically purpose-built - "community_centre", // often purpose-built, but not necessarily - // "conference_centre", // purpose-built - "events_venue", // smaller ones are not purpose-built - // "exhibition_centre", // purpose-built - "gambling", - // "love_hotel", - // "planetarium", // like cinema - "nightclub", - "social_centre", - "stripclub", - "studio", - "swingerclub", - // "theatre", - - /* public service */ - // "courthouse", // purpose-built - // "fire_station", // purpose-built - // "police", // purpose-built - "post_office", - // "ranger_station", // like police station - // "townhall", // purpose-built - - /* other */ - "animal_boarding", // not necessarily purpose-built - // "animal_breeding", - "animal_shelter", // not necessarily purpose-built - "coworking_space", // basically an office - // "embassy", // usually purpose-built / there is also office=diplomatic for those that have services - // "monastery", // purpose-built, often historic - "place_of_worship" // usually-purpose-built, but not always (also, prayer rooms) - ) - ).map { p + it.key + " ~ " + it.value.joinToString("|") }.joinToString("\n or ") + ")\n" - ).trimIndent() -} - -/** Expression to see if an element is some kind of shop, disused or not */ -val IS_SHOP_OR_DISUSED_SHOP_EXPRESSION = """ - nodes, ways, relations with - ${isShopExpressionFragment()} - or ${isShopExpressionFragment("disused")} - or shop = vacant - or office = vacant -""".toElementFilterExpression() - -/** Expression to see if an element is some kind active, non-vacant shop */ -val IS_SHOP_EXPRESSION = - "nodes, ways, relations with ${isShopExpressionFragment()}".toElementFilterExpression() - -/** Expression to see if an element is some kind of vacant shop */ -val IS_DISUSED_SHOP_EXPRESSION = """ - nodes, ways, relations with - ${isShopExpressionFragment("disused")} - or shop = vacant - or office = vacant -""".toElementFilterExpression() diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/Things.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/Things.kt new file mode 100644 index 0000000000..2d27936506 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/Things.kt @@ -0,0 +1,279 @@ +package de.westnordost.streetcomplete.osm + +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression +import de.westnordost.streetcomplete.data.osm.mapdata.Element + +fun Element.isThing(): Boolean = + IS_THING_EXPRESSION.matches(this) + +fun Element.isDisusedThing(): Boolean = + this.asIfItWasnt("disused")?.let { IS_THING_EXPRESSION.matches(it) } == true + +/** Small map features that are often mapped as points and usually cannot be entered. + * + * To demarcate this category from others, the following are not included: + * - structures that are big enough that they would rather be classified alongside buildings + * (e.g. water towers) + * - permanent features of the landscape (e.g. geysers, peaks, springs) + * - things that are features on other map features that wouldn't make sense on their own (e.g. + * crossings, traffic lights, entrances, golf holes) + * - things that should not be mapped as points - even if allowed - and landuses (e.g. car parks, + * dog parks, playgrounds) + * + * Additionally, these things are generally not included: + * - things that are intangible / difficult to verify on-site (e.g. fishing spots) + * - things that would just be madness to comprehensively map (e.g. manhole covers), maybe what's + * madness to map or not is in the eye of the beholder, in this case, in the eyes of app + * maintainers + * + * Important: The contribution policy here is that only things may be added if an iD preset for it + * exists. This policy exists in order to reduce effort to maintain this list, i.e. we + * don't want to check, weigh and balance requests in parallel to iD maintainers (in + * terms of notability, it being unambiguous, consensus etc.) + * */ +private val IS_THING_EXPRESSION by lazy { + val tags = mapOf( + "aeroway" to listOf( + "navigationaid", + "windsock", + ), + "amenity" to listOf( + /* grouped by subcategory, sorted by alphabet */ + + /* transportation */ + "bicycle_parking", + "bicycle_rental", + "bicycle_repair_station", + "bicycle_wash", + "boat_sharing", // like car sharing stations, this is the location of the actual boat(s) + "car_sharing", + "charging_station", + "compressed_air", + "grit_bin", + "hitching_post", + "motorcycle_parking", // a bit like a landuse, but a very small one - similar to bicycle parking + "taxi", + "ticket_validator", + "vacuum_cleaner", + + /* financial */ + "atm", + "payment_terminal", + + /* healthcare */ + "baby_hatch", + "kneipp_water_cure", + + /* entertainment, arts & culture */ + "fountain", + "public_bookcase", + "whirlpool", + + /* public service */ + // "post_box", - blocked by https://github.com/streetcomplete/StreetComplete/issues/4916 + + /* facilities & others */ + "baking_oven", + "bbq", + "bench", + "binoculars", + "clock", + "device_charging_station", + "dog_toilet", + "dressing_room", // though you can enter, it belongs in the same category as toilets + "drinking_water", + "food_sharing", + "give_box", + "karaoke_box", + "kitchen", // usually an amenity within campsites etc, i.e. like shower, toilets, ... + // "letter_box", - see "post_box", but also, it would be very spammy to comprehensively map this + "library_dropoff", + "locker", + "lounger", + "luggage_locker", + "mist_spraying_cooler", + "parcel_locker", + "photo_booth", + "security_booth", + "shelter", + "shower", + "smoking_area", + "stage", // because bandstand is also included, borderline rather a structure + "telephone", + "toilets", + "trolley_bay", + "vending_machine", + "water_point", + "watering_place", + + /* waste management */ + // "recycling" only for containers, see bottom + "sanitary_dump_station", + "waste_basket", + "waste_disposal", + + /* animals */ + "feeding_place", + "game_feeding", + "hunting_stand", + ), + "emergency" to listOf( + "access_point", // (synonym of highway=emergency_access_point) + "assembly_point", + "defibrillator", + // "emergency_ward_entrance" is rather a type of entrance + "fire_alarm_box", + "fire_extinguisher", + "fire_hose", + "fire_hydrant", + "fire_lookout", + "fire_service_inlet", + "first_aid_kit", + "landing_site", // not a helipad but more alike an access_point + "life_ring", + "lifeguard", + "phone", + "siren", + // "suction_point" is rather intangible / difficult to verify + // "water_tank" is more of a structure and would also need to include "fire_water_pond" etc. then + ), + "highway" to listOf( + // "bus_stop", - a bus stop shelter is similar to a shelter, but on the other hand, this + // is used to tag the *platform*, i.e. the shelter (bench, waste basket, + // ...) can also be mapped separately. If this would only be limited to + // points, I guess we could include it, but for any platforms, too much + "cyclist_waiting_aid", + "emergency_access_point", + "milestone", + // "speed_camera", - while not directly a sign, it definitely belongs into the traffic + // signals/controls category + // "speed_display", this is rather like a sign - signs should not go in here + "street_lamp", // candidate to be moved to lit overlay? + "trailhead", + // "traffic_mirror" is rather like a sign - signs should not go in here + ), + "historic" to listOf( + "aircraft", + "anchor", + "boundary_stone", + "cannon", + "locomotive", + "milestone", + "memorial", + // "monument" - it's rather a structure. Small monuments are tagged as "memorial" + "railway_car", + "rune_stone", + // "ship" - probably too big, more like a structure + "stone", + "tank", + "vehicle", + // "wreck" - probably too big, and usually quite off-shore anyway + "wayside_cross", + "wayside_shrine", + ), + "leisure" to listOf( + "bandstand", + "bird_hide", // though it is possible to go inside, it's similar to a shelter + // "bathing_place" - is rather intangible / difficult to verify, + // also would probably need to include "beach_resort", etc. too + "firepit", + // "fishing" is rather intangible / difficult to verify + "fitness_station", + "hot_tub", + "outdoor_seating", + "slipway", + "picnic_table", + "wildlife_hide", // though it is possible to go inside, it's similar to a shelter + ), + "man_made" to listOf( + // larger structures are rather alike buildings, they shouldn't be editable here + // e.g. "water_tower", "watermill", "windmill", "tower", "telescope", "stupa" ... + // "antenna" - I think those small-ish antennas for cellular network would be fine + // but quite large structures also fall under this tag + "beehive", + "cairn", + "carpet_hanger", + "column", + "compass_rose", + "cross", + "dolphin", // a bit of a small structure already, but on the other hand, a "boat parking" + "dovecote", + "flagpole", + "insect_hotel", + // "manhole", - too many of them, it's madness to waste your time mapping these + "maypole", + "monitoring_station", // a little large, on the other hand, sizes vary + "nesting_site", + // "obelisk" - like historic=monument it's more of a structure + "planter", + "snow_cannon", + "stele", + // "street_cabinet", - blocked; see note at amenity=post_box + "surveillance", + // "survey_point" - this can be very very small -> verifiability issue + // danger that mapper deletes it because he can't find it + // "telephone_box" - it just describes the structure, but not its use + // "utility_pole" - usually a vertex + "video_wall", // basically an advertising=* + "water_tap", + "water_well", + ), + "natural" to listOf( + // permanent geology features that are part of the landscape are not included + // "arch", "cave_entrance", "fumarole", "geyser", "hill", "hot_spring", "peak", + // "saddle", "spring", "volcano", ... + "rock", // de-facto synonymous to "stone" + "stone", // theoretically it can be detached from the rest of the landscape + "tree", + "tree_stump", + ), + "tourism" to listOf( + "artwork", + // "information", only if it is not an office, see below + "viewpoint", + ) + ) + .map { it.key + " ~ " + it.value.joinToString("|") } + .joinToString("\n or ") + + """ + nodes, ways, relations with + $tags + or advertising + or amenity = recycling and recycling_type = container + or attraction + or boundary = marker + or leisure = pitch and sport ~ chess|table_soccer|table_tennis|teqball + or man_made = street_cabinet and street_cabinet != postal_service + or playground + or tourism = information and information !~ office|visitor_centre + """.toElementFilterExpression() +} + +val POPULAR_THING_FEATURE_IDS = listOf( + "natural/tree/broadleaved", // 4.0 M + "highway/street_lamp", // 4.0 M + "amenity/bench", // 2.4 M + "emergency/fire_hydrant", // 2.0 M + + "amenity/waste_basket", // 0.7 M + "amenity/bicycle_parking", // 0.6 M + "amenity/shelter", // 0.5 M + + "amenity/recycling_container", // 0.4 M + "amenity/toilets", // 0.4 M + + // "amenity/post_box", // 0.4 M + // blocked by https://github.com/streetcomplete/StreetComplete/issues/4916 + + // More: + + // mostly found in parks/plazas, i.e. specific places instead of ~everywhere + //"historic/memorial", // 0.4 M (if this is displayed in quick select, artwork should probably too) + //"amenity/drinking_water", // 0.3 M + //"leisure/picnic_table", // 0.3 M + + // found most often on hiking routes where there are not that many "things" features anyway + //"information/guidepost", // 0.5M + //"tourism/information/board", // 0.3M +) diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/OverlaysModule.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/OverlaysModule.kt index fc2022b27d..f1a71a37c7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/OverlaysModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/OverlaysModule.kt @@ -6,6 +6,7 @@ import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.data.meta.CountryInfo import de.westnordost.streetcomplete.data.meta.CountryInfos import de.westnordost.streetcomplete.data.meta.getByLocation +import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.overlays.OverlayRegistry import de.westnordost.streetcomplete.overlays.address.AddressOverlay @@ -13,6 +14,7 @@ import de.westnordost.streetcomplete.overlays.buildings.BuildingsOverlay 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.things.ThingsOverlay import de.westnordost.streetcomplete.overlays.street_parking.StreetParkingOverlay import de.westnordost.streetcomplete.overlays.surface.SurfaceOverlay import de.westnordost.streetcomplete.overlays.way_lit.WayLitOverlay @@ -35,17 +37,17 @@ val overlaysModule = module { val countryBoundaries = get>(named("CountryBoundariesLazy")).value countryBoundaries.getIds(location).firstOrNull() }, - { tags -> - get>(named("FeatureDictionaryLazy")).value.getFeature(tags) + { element -> + get>(named("FeatureDictionaryLazy")).value.getFeature(element) } ) } } fun overlaysRegistry( - getCountryInfoByLocation: (location: LatLon) -> CountryInfo, - getCountryCodeByLocation: (location: LatLon) -> String?, - getFeature: (tags: Map) -> Feature?, + getCountryInfoByLocation: (LatLon) -> CountryInfo, + getCountryCodeByLocation: (LatLon) -> String?, + getFeature: (Element) -> Feature?, ) = OverlayRegistry(listOf( 0 to WayLitOverlay(), @@ -55,5 +57,6 @@ fun overlaysRegistry( 2 to StreetParkingOverlay(), 3 to AddressOverlay(getCountryCodeByLocation), 4 to ShopsOverlay(getFeature), - 7 to BuildingsOverlay() + 8 to ThingsOverlay(getFeature), + 7 to BuildingsOverlay(), )) diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlay.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlay.kt index 29005bb60f..e36f551a10 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlay.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlay.kt @@ -5,9 +5,8 @@ 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_SHOP_OR_DISUSED_SHOP_EXPRESSION +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.overlays.Color import de.westnordost.streetcomplete.overlays.Overlay import de.westnordost.streetcomplete.overlays.PointStyle @@ -17,12 +16,12 @@ import de.westnordost.streetcomplete.quests.shop_type.CheckShopType import de.westnordost.streetcomplete.quests.shop_type.SpecifyShopType import de.westnordost.streetcomplete.util.getNameLabel -class ShopsOverlay(private val getFeature: (tags: Map) -> Feature?) : Overlay { +class ShopsOverlay(private val getFeature: (Element) -> Feature?) : Overlay { - override val title = R.string.overlay_shops + override val title = R.string.overlay_places override val icon = R.drawable.ic_quest_shop - override val changesetComment = "Survey shops etc." - override val wikiLink: String = "Key:shop" + override val changesetComment = "Survey shops, places etc." + override val wikiLink = null override val achievements = listOf(EditTypeAchievement.CITIZEN) override val hidesQuestTypes = setOf( AddPlaceName::class.simpleName!!, @@ -38,9 +37,10 @@ class ShopsOverlay(private val getFeature: (tags: Map) -> Featur override fun getStyledElements(mapData: MapDataWithGeometry) = mapData - .filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + .asSequence() + .filter { it.isPlaceOrDisusedShop() } .map { element -> - val feature = getFeature(element.tags) + val feature = getFeature(element) val icon = "ic_preset_" + (feature?.icon ?: "maki-shop").replace('-', '_') val label = getNameLabel(element.tags) diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlayForm.kt index a8d87ef6f4..5c93f45898 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlayForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/ShopsOverlayForm.kt @@ -5,6 +5,7 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType import de.westnordost.streetcomplete.Prefs.PREFERRED_LANGUAGE_FOR_NAMES import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction @@ -13,18 +14,21 @@ import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChanges 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.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.databinding.FragmentOverlayShopsBinding -import de.westnordost.streetcomplete.osm.IS_DISUSED_SHOP_EXPRESSION -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.LocalizedName import de.westnordost.streetcomplete.osm.applyTo import de.westnordost.streetcomplete.osm.parseLocalizedNames -import de.westnordost.streetcomplete.osm.replaceShop +import de.westnordost.streetcomplete.osm.replacePlace import de.westnordost.streetcomplete.overlays.AbstractOverlayForm import de.westnordost.streetcomplete.overlays.AnswerItem import de.westnordost.streetcomplete.quests.LocalizedNameAdapter +import de.westnordost.streetcomplete.osm.POPULAR_PLACE_FEATURE_IDS +import de.westnordost.streetcomplete.osm.isDisusedPlace +import de.westnordost.streetcomplete.osm.isPlace +import de.westnordost.streetcomplete.util.DummyFeature import de.westnordost.streetcomplete.util.getLocalesForFeatureDictionary import de.westnordost.streetcomplete.util.getLocationLabel import de.westnordost.streetcomplete.util.ktx.geometryType @@ -65,13 +69,16 @@ class ShopsOverlayForm : AbstractOverlayForm() { val element = element originalFeature = element?.let { - if (IS_DISUSED_SHOP_EXPRESSION.matches(element)) { - featureDictionary.byId("shop/vacant").get() + val locales = getLocalesForFeatureDictionary(resources.configuration) + val geometryType = if (element.type == ElementType.NODE) null else element.geometryType + + if (element.isDisusedPlace()) { + featureDictionary.byId("shop/vacant").forLocale(*locales).get() } else { featureDictionary .byTags(element.tags) - .forLocale(*getLocalesForFeatureDictionary(resources.configuration)) - .forGeometry(element.geometryType) + .forLocale(*locales) + .forGeometry(geometryType) .inCountry(countryOrSubdivisionCode) .find() .firstOrNull() @@ -103,11 +110,12 @@ class ShopsOverlayForm : AbstractOverlayForm() { SearchFeaturesDialog( requireContext(), featureDictionary, - element?.geometryType, + element?.geometryType ?: GeometryType.POINT, countryOrSubdivisionCode, featureCtrl.feature?.name, ::filterOnlyShops, - ::onSelectedFeature + ::onSelectedFeature, + POPULAR_PLACE_FEATURE_IDS, ).show() } @@ -150,7 +158,7 @@ class ShopsOverlayForm : AbstractOverlayForm() { private fun filterOnlyShops(feature: Feature): Boolean { val fakeElement = Node(-1L, LatLon(0.0, 0.0), feature.tags, 0) - return IS_SHOP_OR_DISUSED_SHOP_EXPRESSION.matches(fakeElement) + return fakeElement.isPlace() || feature.id == "shop/vacant" } private fun onSelectedFeature(feature: Feature) { @@ -256,7 +264,7 @@ private suspend fun createEditAction( val hasChangedFeature = newFeature != previousFeature val isFeatureWithName = newFeature.addTags?.get("name") != null val wasFeatureWithName = previousFeature?.addTags?.get("name") != null - val wasVacant = element != null && IS_DISUSED_SHOP_EXPRESSION.matches(element) + val wasVacant = element != null && element.isDisusedPlace() val isVacant = newFeature.id == "shop/vacant" val doReplaceShop = @@ -292,7 +300,7 @@ private suspend fun createEditAction( } if (doReplaceShop) { - tagChanges.replaceShop(newFeature.addTags) + tagChanges.replacePlace(newFeature.addTags) } else { for ((key, value) in previousFeature?.removeTags.orEmpty()) { tagChanges.remove(key) @@ -312,7 +320,7 @@ private suspend fun createEditAction( } return if (element != null) { - UpdateElementTagsAction(element!!, tagChanges.create()) + UpdateElementTagsAction(element, tagChanges.create()) } else { CreateNodeAction(geometry.center, tagChanges) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/surface/SurfaceOverlayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/surface/SurfaceOverlayForm.kt index ce678f5a39..12d931a02e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/surface/SurfaceOverlayForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/surface/SurfaceOverlayForm.kt @@ -26,7 +26,7 @@ import de.westnordost.streetcomplete.overlays.AbstractOverlayForm import de.westnordost.streetcomplete.overlays.AnswerItem import de.westnordost.streetcomplete.overlays.IAnswerItem import de.westnordost.streetcomplete.util.LastPickedValuesStore -import de.westnordost.streetcomplete.util.getFeatureName +import de.westnordost.streetcomplete.util.getLocalesForFeatureDictionary import de.westnordost.streetcomplete.util.ktx.couldBeSteps import de.westnordost.streetcomplete.util.prefs.Preferences import de.westnordost.streetcomplete.view.setImage @@ -143,9 +143,9 @@ class SurfaceOverlayForm : AbstractOverlayForm() { switchToFootwayCyclewaySurfaceLayout() } - val conf = resources.configuration - binding.cyclewaySurfaceLabel.text = featureDictionary.getFeatureName(conf, mapOf("highway" to "cycleway"), GeometryType.LINE) - binding.footwaySurfaceLabel.text = featureDictionary.getFeatureName(conf, mapOf("highway" to "footway"), GeometryType.LINE) + val locales = getLocalesForFeatureDictionary(resources.configuration) + binding.cyclewaySurfaceLabel.text = featureDictionary.byId("highway/cycleway").forLocale(*locales).get()?.name + binding.footwaySurfaceLabel.text = featureDictionary.byId("highway/footway").forLocale(*locales).get()?.name checkIsFormComplete() } diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlay.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlay.kt new file mode 100644 index 0000000000..26202a2e19 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlay.kt @@ -0,0 +1,51 @@ +package de.westnordost.streetcomplete.overlays.things + +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.user.achievements.EditTypeAchievement +import de.westnordost.streetcomplete.osm.asIfItWasnt +import de.westnordost.streetcomplete.osm.isDisusedThing +import de.westnordost.streetcomplete.osm.isThing +import de.westnordost.streetcomplete.overlays.Color +import de.westnordost.streetcomplete.overlays.Overlay +import de.westnordost.streetcomplete.overlays.PointStyle +import de.westnordost.streetcomplete.overlays.PolygonStyle + +class ThingsOverlay(private val getFeature: (Element) -> Feature?) : Overlay { + + override val title = R.string.overlay_things + override val icon = R.drawable.ic_quest_dot + override val changesetComment = "Survey small map features" + override val wikiLink = null + 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 + .asSequence() + .filter { it.isThing() || it.isDisusedThing() } + .mapNotNull { element -> + val feature = getFeature(element) + ?: element.asIfItWasnt("disused")?.let { getFeature(it) } + ?: return@mapNotNull null + + val icon = "ic_preset_" + (feature.icon ?: "maki-marker-stroked").replace('-', '_') + + val style = if (element is Node) { + PointStyle(icon) + } else { + PolygonStyle(Color.INVISIBLE, icon) + } + element to style + } + + override fun createForm(element: Element?) = ThingsOverlayForm() +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlayForm.kt new file mode 100644 index 0000000000..a12487ca3d --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/things/ThingsOverlayForm.kt @@ -0,0 +1,156 @@ +package de.westnordost.streetcomplete.overlays.things + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isGone +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType +import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.data.osm.edits.create.CreateNodeAction +import de.westnordost.streetcomplete.data.osm.edits.delete.DeletePoiNodeAction +import de.westnordost.streetcomplete.data.osm.mapdata.Element +import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.data.osm.mapdata.Node +import de.westnordost.streetcomplete.databinding.FragmentOverlayThingsBinding +import de.westnordost.streetcomplete.osm.POPULAR_THING_FEATURE_IDS +import de.westnordost.streetcomplete.osm.asIfItWasnt +import de.westnordost.streetcomplete.osm.isThing +import de.westnordost.streetcomplete.overlays.AbstractOverlayForm +import de.westnordost.streetcomplete.overlays.AnswerItem +import de.westnordost.streetcomplete.overlays.IAnswerItem +import de.westnordost.streetcomplete.util.DummyFeature +import de.westnordost.streetcomplete.util.getLocalesForFeatureDictionary +import de.westnordost.streetcomplete.util.getLocationLabel +import de.westnordost.streetcomplete.util.ktx.geometryType +import de.westnordost.streetcomplete.view.controller.FeatureViewController +import de.westnordost.streetcomplete.view.dialogs.SearchFeaturesDialog + +class ThingsOverlayForm : AbstractOverlayForm() { + + override val contentLayoutResId = R.layout.fragment_overlay_things + private val binding by contentViewBinding(FragmentOverlayThingsBinding::bind) + + private var originalFeature: Feature? = null + + private lateinit var featureCtrl: FeatureViewController + + override val otherAnswers get() = listOfNotNull( + createDeletePoiAnswer() + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + originalFeature = getOriginalFeature() + } + + private fun getOriginalFeature(): Feature? { + val element = element ?: return null + val feature = getFeatureDictionaryFeature(element) + if (feature != null) return feature + + val disusedElement = element.asIfItWasnt("disused") + if (disusedElement != null) { + val disusedFeature = getFeatureDictionaryFeature(disusedElement) + if (disusedFeature != null) { + return DummyFeature( + disusedFeature.id + "/disused", + "${disusedFeature.name} (${resources.getString(R.string.disused).uppercase()})", + disusedFeature.icon, + disusedFeature.addTags.mapKeys { "disused:${it.key}" } + ) + } + } + + return DummyFeature( + "thing/unknown", + requireContext().getString(R.string.unknown_object), + "ic_preset_maki_marker_stroked", + element.tags + ) + } + + private fun getFeatureDictionaryFeature(element: Element): Feature? { + val locales = getLocalesForFeatureDictionary(resources.configuration) + val geometryType = if (element.type == ElementType.NODE) null else element.geometryType + + return featureDictionary + .byTags(element.tags) + .forLocale(*locales) + .forGeometry(geometryType) + .inCountry(countryOrSubdivisionCode) + .find() + .firstOrNull() + } + + 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_dot) + + featureCtrl = FeatureViewController(featureDictionary, binding.featureTextView, binding.featureIconView) + featureCtrl.countryOrSubdivisionCode = countryOrSubdivisionCode + featureCtrl.feature = originalFeature + + // editing an existing feature is disabled because unlike shops, they don't just change + // (e.g. a photo booth rarely transforms into a fountain). If something doesn't exist, it + // should simply be deleted + + if (element == null) { + binding.featureView.setOnClickListener { showFeatureSelectionDialog() } + } else { + binding.featureDropdownButton.isGone = true + binding.featureView.background = null + } + } + + private fun showFeatureSelectionDialog() { + SearchFeaturesDialog( + requireContext(), + featureDictionary, + element?.geometryType ?: GeometryType.POINT, // for new features: always POINT + countryOrSubdivisionCode, + featureCtrl.feature?.name, + ::filterOnlyThings, + ::onSelectedFeature, + POPULAR_THING_FEATURE_IDS + ).show() + } + + private fun filterOnlyThings(feature: Feature): Boolean { + val fakeElement = Node(-1L, LatLon(0.0, 0.0), feature.tags, 0) + return fakeElement.isThing() + } + + private fun onSelectedFeature(feature: Feature) { + featureCtrl.feature = feature + checkIsFormComplete() + } + + private fun createDeletePoiAnswer(): IAnswerItem? { + val node = element as? Node ?: return null + return AnswerItem(R.string.quest_generic_answer_does_not_exist) { confirmDelete(node) } + } + + private fun confirmDelete(node: Node) { + AlertDialog.Builder(requireContext()) + .setMessage(R.string.osm_element_gone_description) + .setPositiveButton(R.string.osm_element_gone_confirmation) { _, _ -> applyEdit(DeletePoiNodeAction(node)) } + .setNeutralButton(R.string.leave_note) { _, _ -> composeNote(node) } + .show() + } + + override fun hasChanges(): Boolean = originalFeature != featureCtrl.feature + + override fun isFormComplete(): Boolean = featureCtrl.feature != null + + override fun onClickOk() { + if (element == null) { + val feature = featureCtrl.feature!! + applyEdit(CreateNodeAction(geometry.center, feature.addTags)) + } + } +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt index b710d0c725..5d5920d169 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt @@ -36,8 +36,8 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestsHiddenControlle import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsController import de.westnordost.streetcomplete.data.quest.OsmQuestKey -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION -import de.westnordost.streetcomplete.osm.replaceShop +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop +import de.westnordost.streetcomplete.osm.replacePlace import de.westnordost.streetcomplete.quests.shop_type.ShopGoneDialog import de.westnordost.streetcomplete.util.getNameAndLocationLabel import de.westnordost.streetcomplete.util.ktx.geometryType @@ -158,17 +158,17 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta private fun createDeleteOrReplaceElementAnswer(): AnswerItem? { val isDeletePoiEnabled = osmElementQuestType.isDeleteElementEnabled && element.type == ElementType.NODE - val isReplaceShopEnabled = osmElementQuestType.isReplaceShopEnabled - if (!isDeletePoiEnabled && !isReplaceShopEnabled) return null - check(!(isDeletePoiEnabled && isReplaceShopEnabled)) { + val isReplacePlaceEnabled = osmElementQuestType.isReplacePlaceEnabled + if (!isDeletePoiEnabled && !isReplacePlaceEnabled) return null + check(!(isDeletePoiEnabled && isReplacePlaceEnabled)) { "Only isDeleteElementEnabled OR isReplaceShopEnabled may be true at the same time" } return AnswerItem(R.string.quest_generic_answer_does_not_exist) { if (isDeletePoiEnabled) { deletePoiNode() - } else if (isReplaceShopEnabled) { - replaceShop() + } else if (isReplacePlaceEnabled) { + replacePlace() } } } @@ -256,8 +256,8 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta } } - protected fun replaceShop() { - if (IS_SHOP_OR_DISUSED_SHOP_EXPRESSION.matches(element)) { + protected fun replacePlace() { + if (element.isPlaceOrDisusedShop()) { ShopGoneDialog( requireContext(), element.geometryType, @@ -274,7 +274,7 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta private fun onShopReplacementSelected(tags: Map) { viewLifecycleScope.launch { val builder = StringMapChangesBuilder(element.tags) - builder.replaceShop(tags) + builder.replacePlace(tags) solve(UpdateElementTagsAction(element, builder.create())) } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestsModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestsModule.kt index ad02ec2491..fa4a1abe4a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestsModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestsModule.kt @@ -6,6 +6,7 @@ import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.data.meta.CountryInfo import de.westnordost.streetcomplete.data.meta.CountryInfos import de.westnordost.streetcomplete.data.meta.getByLocation +import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry @@ -192,8 +193,8 @@ val questsModule = module { val countryBoundaries = get>(named("CountryBoundariesLazy")).value countryInfos.getByLocation(countryBoundaries, location.longitude, location.latitude) }, - { tags -> - get>(named("FeatureDictionaryLazy")).value.getFeature(tags) + { element -> + get>(named("FeatureDictionaryLazy")).value.getFeature(element) } ) } @@ -203,8 +204,8 @@ fun questTypeRegistry( trafficFlowSegmentsApi: TrafficFlowSegmentsApi, trafficFlowDao: WayTrafficFlowDao, arSupportChecker: ArSupportChecker, - getCountryInfoByLocation: (location: LatLon) -> CountryInfo, - getFeature: (tags: Map) -> Feature?, + getCountryInfoByLocation: (LatLon) -> CountryInfo, + getFeature: (Element) -> Feature?, ) = QuestTypeRegistry(listOf( /* The quest types are primarily sorted by how easy they can be solved: diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cards/AddAcceptsCards.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cards/AddAcceptsCards.kt index 3bc89eb19b..65dd517c96 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cards/AddAcceptsCards.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cards/AddAcceptsCards.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.util.ktx.toYesNo class AddAcceptsCards : OsmFilterQuestType() { @@ -27,14 +26,14 @@ class AddAcceptsCards : OsmFilterQuestType() { override val changesetComment = "Survey whether payment with cards is accepted" override val wikiLink = "Key:payment" override val icon = R.drawable.ic_quest_card - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside override fun getTitle(tags: Map) = R.string.quest_accepts_cards override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddAcceptsCardsForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt index a21ad0397f..7bcf5444f8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt @@ -4,12 +4,11 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.quests.YesNoQuestForm import de.westnordost.streetcomplete.util.ktx.toYesNo @@ -54,7 +53,7 @@ class AddAcceptsCash : OsmFilterQuestType() { override val changesetComment = "Survey whether payment with cash is accepted" override val wikiLink = "Key:payment" override val icon = R.drawable.ic_quest_cash - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val enabledInCountries = NoCountriesExcept( "GB", // https://github.com/streetcomplete/StreetComplete/issues/4517 "SE", @@ -66,7 +65,7 @@ class AddAcceptsCash : OsmFilterQuestType() { override fun getTitle(tags: Map) = R.string.quest_accepts_cash_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = YesNoQuestForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/air_conditioning/AddAirConditioning.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/air_conditioning/AddAirConditioning.kt index ad3f5777be..c41aede435 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/air_conditioning/AddAirConditioning.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/air_conditioning/AddAirConditioning.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.quests.YesNoQuestForm import de.westnordost.streetcomplete.util.ktx.toYesNo @@ -27,14 +26,14 @@ class AddAirConditioning : OsmFilterQuestType() { override val changesetComment = "Survey availability of air conditioning" override val wikiLink = "Key:air_conditioning" override val icon = R.drawable.ic_quest_snow_poi - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside_regional_warning override fun getTitle(tags: Map) = R.string.quest_airConditioning_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = YesNoQuestForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_cover/AddAmenityCover.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_cover/AddAmenityCover.kt index de7f3c6f0d..f06111aae0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_cover/AddAmenityCover.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_cover/AddAmenityCover.kt @@ -10,10 +10,11 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.OUTDOORS import de.westnordost.streetcomplete.osm.Tags import de.westnordost.streetcomplete.quests.YesNoQuestForm +import de.westnordost.streetcomplete.util.ktx.containsAll import de.westnordost.streetcomplete.util.ktx.toYesNo class AddAmenityCover( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { private val nodesFilter by lazy { """ @@ -31,19 +32,17 @@ class AddAmenityCover( override val achievements = listOf(OUTDOORS) override fun getTitle(tags: Map) = R.string.quest_amenityCover_title + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) = - nodesFilter.matches(element) && hasAnyName(element.tags) - - private fun hasAnyName(tags: Map) = getFeature(tags) != null + nodesFilter.matches(element) && getFeature(element) != null override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry): Sequence { /* put markers for objects that are exactly the same as for which this quest is asking for e.g. it's a ticket validator? -> display other ticket validators. Etc. */ - val feature = getFeature(element.tags) ?: return emptySequence() - + val feature = getFeature(element) ?: return emptySequence() return getMapData().filter { it.tags.containsAll(feature.tags) }.asSequence() } @@ -53,5 +52,3 @@ class AddAmenityCover( tags["covered"] = answer.toYesNo() } } - -private fun Map.containsAll(other: Map) = other.all { this[it.key] == it.value } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_indoor/AddIsAmenityIndoor.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_indoor/AddIsAmenityIndoor.kt index 2299fc9d20..2edd22ad03 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_indoor/AddIsAmenityIndoor.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/amenity_indoor/AddIsAmenityIndoor.kt @@ -11,13 +11,14 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.* import de.westnordost.streetcomplete.osm.Tags import de.westnordost.streetcomplete.quests.YesNoQuestForm +import de.westnordost.streetcomplete.util.ktx.containsAll import de.westnordost.streetcomplete.util.ktx.toYesNo import de.westnordost.streetcomplete.util.math.LatLonRaster import de.westnordost.streetcomplete.util.math.contains import de.westnordost.streetcomplete.util.math.isCompletelyInside import de.westnordost.streetcomplete.util.math.isInMultipolygon -class AddIsAmenityIndoor(private val getFeature: (tags: Map) -> Feature?) : +class AddIsAmenityIndoor(private val getFeature: (Element) -> Feature?) : OsmElementQuestType { private val nodesFilter by lazy { """ @@ -52,9 +53,7 @@ class AddIsAmenityIndoor(private val getFeature: (tags: Map) -> override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val bbox = mapData.boundingBox ?: return listOf() - val nodes = mapData.nodes.filter { - nodesFilter.matches(it) && hasAnyName(it.tags) - } + val nodes = mapData.nodes.filter { nodesFilter.matches(it) && getFeature(it) != null } val buildings = mapData.filter { buildingFilter.matches(it) }.toMutableList() val buildingGeometriesById = buildings.associate { @@ -89,23 +88,16 @@ class AddIsAmenityIndoor(private val getFeature: (tags: Map) -> } override fun isApplicableTo(element: Element) = - if (nodesFilter.matches(element) && hasAnyName(element.tags)) { - if (nodesOnWalls.matches(element)) { - true - } else { - null - } + if (nodesFilter.matches(element) && getFeature(element) != null) { + if (nodesOnWalls.matches(element)) true else null } else { false } - private fun hasAnyName(tags: Map) = getFeature(tags) != null - override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry): Sequence { /* put markers for objects that are exactly the same as for which this quest is asking for e.g. it's a ticket validator? -> display other ticket validators. Etc. */ - val feature = getFeature(element.tags) ?: return emptySequence() - + val feature = getFeature(element) ?: return emptySequence() return getMapData().filter { it.tags.containsAll(feature.tags) }.asSequence() } @@ -115,5 +107,3 @@ class AddIsAmenityIndoor(private val getFeature: (tags: Map) -> tags["indoor"] = answer.toYesNo() } } - -private fun Map.containsAll(other: Map) = other.all { this[it.key] == it.value } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt index 2466ecd9ba..c2fb6a0e94 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt @@ -24,7 +24,7 @@ class AddBabyChangingTable : OsmFilterQuestType() { override val changesetComment = "Survey availability of baby changing tables" override val wikiLink = "Key:changing_table" override val icon = R.drawable.ic_quest_baby - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddBikeRepairAvailability.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddBikeRepairAvailability.kt index fb6de5ce16..3e7524304b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddBikeRepairAvailability.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddBikeRepairAvailability.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.BICYCLIST -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate import de.westnordost.streetcomplete.quests.YesNoQuestForm import de.westnordost.streetcomplete.util.ktx.toYesNo @@ -33,7 +32,7 @@ class AddBikeRepairAvailability : OsmFilterQuestType() { override fun getTitle(tags: Map) = R.string.quest_bicycle_shop_repair_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = YesNoQuestForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddSecondHandBicycleAvailability.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddSecondHandBicycleAvailability.kt index a802005b5c..f88a03768b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddSecondHandBicycleAvailability.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_shop/AddSecondHandBicycleAvailability.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.BICYCLIST -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddSecondHandBicycleAvailability : OsmFilterQuestType() { @@ -29,14 +28,14 @@ class AddSecondHandBicycleAvailability : OsmFilterQuestType) = R.string.quest_bicycle_shop_second_hand_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddSecondHandBicycleAvailabilityForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddHalal.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddHalal.kt index 625807024d..0eadbeca77 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddHalal.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddHalal.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddHalal : OsmFilterQuestType() { @@ -28,14 +27,14 @@ class AddHalal : OsmFilterQuestType() { override val changesetComment = "Specify whether places are halal" override val wikiLink = "Key:diet:halal" override val icon = R.drawable.ic_quest_halal - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside_regional_warning override fun getTitle(tags: Map) = R.string.quest_dietType_halal_name_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddDietTypeForm.create(R.string.quest_dietType_explanation_halal) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddKosher.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddKosher.kt index 7a06264a41..62bf7e3ad9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddKosher.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddKosher.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddKosher : OsmFilterQuestType() { @@ -28,14 +27,14 @@ class AddKosher : OsmFilterQuestType() { override val changesetComment = "Specify whether places are kosher" override val wikiLink = "Key:diet:kosher" override val icon = R.drawable.ic_quest_kosher - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside_regional_warning override fun getTitle(tags: Map) = R.string.quest_dietType_kosher_name_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddDietTypeForm.create(R.string.quest_dietType_explanation_kosher) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt index a2e4edaa8e..8752a25321 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt @@ -4,12 +4,11 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.VEG -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddVegan : OsmFilterQuestType() { @@ -32,14 +31,14 @@ class AddVegan : OsmFilterQuestType() { override val changesetComment = "Survey whether places have vegan food" override val wikiLink = "Key:diet" override val icon = R.drawable.ic_quest_restaurant_vegan - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(VEG, CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside override fun getTitle(tags: Map) = R.string.quest_dietType_vegan_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddDietTypeForm.create(R.string.quest_dietType_explanation_vegan) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt index ad33854a10..995fd6ad24 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt @@ -4,12 +4,11 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.VEG -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddVegetarian : OsmFilterQuestType() { @@ -28,14 +27,14 @@ class AddVegetarian : OsmFilterQuestType() { override val changesetComment = "Survey whether places have vegetarian food" override val wikiLink = "Key:diet" override val icon = R.drawable.ic_quest_restaurant_vegetarian - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(VEG, CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside override fun getTitle(tags: Map) = R.string.quest_dietType_vegetarian_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddDietTypeForm.create(R.string.quest_dietType_explanation_vegetarian) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/existence/CheckExistence.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/existence/CheckExistence.kt index fb51a27970..14b7e3b3e7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/existence/CheckExistence.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/existence/CheckExistence.kt @@ -12,9 +12,10 @@ import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement. import de.westnordost.streetcomplete.osm.LAST_CHECK_DATE_KEYS import de.westnordost.streetcomplete.osm.Tags import de.westnordost.streetcomplete.osm.updateCheckDate +import de.westnordost.streetcomplete.util.ktx.containsAll class CheckExistence( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { private val nodesFilter by lazy { """ @@ -99,12 +100,12 @@ class CheckExistence( mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) = - nodesFilter.matches(element) && hasAnyName(element.tags) + nodesFilter.matches(element) && getFeature(element) != null override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry): Sequence { /* put markers for objects that are exactly the same as for which this quest is asking for e.g. it's a ticket validator? -> display other ticket validators. Etc. */ - val feature = getFeature(element.tags) ?: return emptySequence() + val feature = getFeature(element) ?: return emptySequence() return getMapData().filter { it.tags.containsAll(feature.tags) }.asSequence() } @@ -118,8 +119,4 @@ class CheckExistence( older today -$yearsAgo years or ${LAST_CHECK_DATE_KEYS.joinToString(" or ") { "$it < today -$yearsAgo years" }} """.trimIndent() - - private fun hasAnyName(tags: Map) = getFeature(tags) != null } - -private fun Map.containsAll(other: Map) = other.all { this[it.key] == it.value } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/hairdresser/AddHairdresserCustomers.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/hairdresser/AddHairdresserCustomers.kt index e46fa3add3..b870d6f37b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/hairdresser/AddHairdresserCustomers.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/hairdresser/AddHairdresserCustomers.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop class AddHairdresserCustomers : OsmFilterQuestType() { @@ -23,13 +22,13 @@ class AddHairdresserCustomers : OsmFilterQuestType() { override val changesetComment = "Survey hairdresser's customers" override val wikiLink = "Tag:shop=hairdresser" override val icon = R.drawable.ic_quest_hairdresser - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override fun getTitle(tags: Map) = R.string.quest_hairdresser_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddHairdresserCustomersForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevel.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevel.kt index 8adde14bde..5ad601ffba 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevel.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevel.kt @@ -9,7 +9,7 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN import de.westnordost.streetcomplete.osm.Tags -import de.westnordost.streetcomplete.osm.isShopExpressionFragment +import de.westnordost.streetcomplete.osm.isPlace import de.westnordost.streetcomplete.util.math.contains import de.westnordost.streetcomplete.util.math.isInMultipolygon @@ -34,9 +34,8 @@ class AddLevel : OsmElementQuestType { * outline */ private val filter by lazy { """ nodes with - (${isShopExpressionFragment()}) - and !level - and (name or brand or noname = yes or name:signed = no) + !level + and (name or brand or noname = yes or name:signed = no) """.toElementFilterExpression() } override val changesetComment = "Determine on which level shops are in a building" @@ -45,7 +44,7 @@ class AddLevel : OsmElementQuestType { /* disabled because in a mall with multiple levels, if there are nodes with no level defined, * it really makes no sense to tag something as vacant if the level is not known. Instead, if * the user cannot find the place on any level in the mall, delete the element completely. */ - override val isReplaceShopEnabled = false + override val isReplacePlaceEnabled = false override val isDeleteElementEnabled = true override val achievements = listOf(CITIZEN) @@ -84,7 +83,7 @@ class AddLevel : OsmElementQuestType { // now, return all shops that have no level tagged and are inside those multi-level malls val shopsWithoutLevel = mapData - .filter { filter.matches(it) } + .filter { filter.matches(it) && it.isPlace() } .toMutableList() if (shopsWithoutLevel.isEmpty()) return emptyList() @@ -106,7 +105,7 @@ class AddLevel : OsmElementQuestType { } override fun isApplicableTo(element: Element): Boolean? { - if (!filter.matches(element)) return false + if (!filter.matches(element) || !element.isPlace()) return false // for shops with no level, we actually need to look at geometry in order to find if it is // contained within any multi-level mall return null diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevelForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevelForm.kt index d35a319c0e..369fba621c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevelForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/level/AddLevelForm.kt @@ -8,7 +8,7 @@ import de.westnordost.streetcomplete.data.osm.edits.MapDataWithEditsSource import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.databinding.QuestLevelBinding -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.level.SingleLevel import de.westnordost.streetcomplete.osm.level.levelsIntersect import de.westnordost.streetcomplete.osm.level.parseLevelsOrNull @@ -55,7 +55,7 @@ class AddLevelForm : AbstractOsmQuestForm() { val mapData = withContext(Dispatchers.IO) { mapDataSource.getMapDataWithGeometry(bbox) } val shopsWithLevels = mapData.filter { - it.tags["level"] != null && IS_SHOP_OR_DISUSED_SHOP_EXPRESSION.matches(it) + it.tags["level"] != null && it.isPlaceOrDisusedShop() } shopElementsAndGeometry = shopsWithLevels.mapNotNull { e -> @@ -100,7 +100,7 @@ class AddLevelForm : AbstractOsmQuestForm() { val levels = listOf(SingleLevel(level)) for ((element, geometry) in shopElementsAndGeometry) { if (!parseLevelsOrNull(element.tags).levelsIntersect(levels)) continue - val icon = getPinIcon(featureDictionary, element.tags) + val icon = getPinIcon(featureDictionary, element) val title = getTitle(element.tags) showsGeometryMarkersListener?.putMarkerForCurrentHighlighting(geometry, icon, title) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 7e1dbc48b8..8942725da4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -9,17 +9,16 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.opening_hours.parser.isSupportedOpeningHours import de.westnordost.streetcomplete.osm.updateCheckDateForKey import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddOpeningHours( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { /* See also AddWheelchairAccessBusiness and AddPlaceName, which has a similar list and is/should @@ -123,7 +122,7 @@ class AddOpeningHours( override val changesetComment = "Survey opening hours" override val wikiLink = "Key:opening_hours" override val icon = R.drawable.ic_quest_opening_hours - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) private val olderThan1Year = TagOlderThan("opening_hours", RelativeDate(-365f)) @@ -144,11 +143,10 @@ class AddOpeningHours( override fun isApplicableTo(element: Element): Boolean { if (!filter.matches(element)) return false - val tags = element.tags // only show places that can be named somehow - if (!hasName(tags)) return false + if (!hasName(element)) return false // no opening_hours yet -> new survey - val ohStr = tags["opening_hours"] ?: return true + val ohStr = element.tags["opening_hours"] ?: return true /* don't show if it was recently checked (actually already checked by filter, but it is a performance improvement to avoid parsing the opening hours en masse if possible) */ if (!olderThan1Year.matches(element)) return false @@ -161,7 +159,7 @@ class AddOpeningHours( } override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddOpeningHoursForm() @@ -185,10 +183,10 @@ class AddOpeningHours( tags.remove("opening_hours:covid19") } - private fun hasName(tags: Map) = hasProperName(tags) || hasFeatureName(tags) + private fun hasName(element: Element) = hasProperName(element.tags) || hasFeatureName(element) private fun hasProperName(tags: Map): Boolean = tags.containsKey("name") || tags.containsKey("brand") - private fun hasFeatureName(tags: Map) = getFeature(tags) != null + private fun hasFeatureName(element: Element) = getFeature(element)?.name != null } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSigned.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSigned.kt index 82376fa307..9c6a468033 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSigned.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSigned.kt @@ -6,12 +6,11 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags import de.westnordost.streetcomplete.osm.getLastCheckDateKeys +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.setCheckDateForKey import de.westnordost.streetcomplete.osm.toCheckDate import de.westnordost.streetcomplete.osm.updateCheckDateForKey @@ -21,7 +20,7 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime class CheckOpeningHoursSigned( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { private val filter by lazy { """ @@ -46,7 +45,7 @@ class CheckOpeningHoursSigned( override val changesetComment = "Survey whether opening hours are signed" override val wikiLink = "Key:opening_hours:signed" override val icon = R.drawable.ic_quest_opening_hours_signed - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override fun getTitle(tags: Map) = R.string.quest_openingHours_signed_title @@ -55,10 +54,10 @@ class CheckOpeningHoursSigned( mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element): Boolean = - filter.matches(element) && hasName(element.tags) + filter.matches(element) && hasName(element) override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = YesNoQuestForm() @@ -85,10 +84,10 @@ class CheckOpeningHoursSigned( } } - private fun hasName(tags: Map) = hasProperName(tags) || hasFeatureName(tags) + private fun hasName(element: Element) = hasProperName(element.tags) || hasFeatureName(element) private fun hasProperName(tags: Map): Boolean = tags.containsKey("name") || tags.containsKey("brand") - private fun hasFeatureName(tags: Map) = getFeature(tags) != null + private fun hasFeatureName(element: Element) = getFeature(element)?.name != null } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt index 29454eafcb..3f3da9e2a1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt @@ -6,15 +6,14 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags import de.westnordost.streetcomplete.osm.applyTo +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop class AddPlaceName( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { private val filter by lazy { (""" @@ -114,7 +113,7 @@ class AddPlaceName( override val changesetComment = "Determine place names" override val wikiLink = "Key:name" override val icon = R.drawable.ic_quest_label - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override fun getTitle(tags: Map) = R.string.quest_placeName_title @@ -123,10 +122,10 @@ class AddPlaceName( mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element): Boolean = - filter.matches(element) && hasFeatureName(element.tags) + filter.matches(element) && getFeature(element) != null override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddPlaceNameForm() @@ -140,6 +139,4 @@ class AddPlaceName( } } } - - private fun hasFeatureName(tags: Map) = getFeature(tags) != null } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/seating/AddSeating.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/seating/AddSeating.kt index cd0decb147..d2dc3371dc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/seating/AddSeating.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/seating/AddSeating.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.util.ktx.toYesNo class AddSeating : OsmFilterQuestType() { @@ -25,14 +24,14 @@ class AddSeating : OsmFilterQuestType() { override val changesetComment = "Survey whether places have seating" override val wikiLink = "Key:outdoor_seating" override val icon = R.drawable.ic_quest_seating - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_seasonal override fun getTitle(tags: Map) = R.string.quest_seating_name_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddSeatingForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt index e5c1ab097c..83eb0ae299 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt @@ -15,7 +15,7 @@ class AddSelfServiceLaundry : OsmFilterQuestType() { override val changesetComment = "Survey whether laundries provide self-service" override val wikiLink = "Tag:shop=laundry" override val icon = R.drawable.ic_quest_laundry - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override fun getTitle(tags: Map) = R.string.quest_laundrySelfService_title2 diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistence.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistence.kt index bf3e395548..f27eed094d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistence.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistence.kt @@ -6,37 +6,35 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.LAST_CHECK_DATE_KEYS import de.westnordost.streetcomplete.osm.Tags -import de.westnordost.streetcomplete.osm.isShopExpressionFragment +import de.westnordost.streetcomplete.osm.isPlace +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateCheckDate class CheckShopExistence( - private val getFeature: (tags: Map) -> Feature? + private val getFeature: (Element) -> Feature? ) : OsmElementQuestType { // opening hours quest acts as a de facto checker of shop existence, but some people disabled it. // separate from CheckExistence as very old shop with opening hours should show // opening hours resurvey quest rather than this one (which would cause edit date to be changed // and silence all resurvey quests) private val filter by lazy { (""" - nodes, ways with ( - ${isShopExpressionFragment()} - and !man_made - and !historic - and !military - and !power - and !attraction - and !aeroway - and !railway - ) and ( - older today -2 years - or ${LAST_CHECK_DATE_KEYS.joinToString(" or ") { "$it < today -2 years" }} - ) - and (name or brand or noname = yes or name:signed = no) + nodes, ways with + !man_made + and !historic + and !military + and !power + and !attraction + and !aeroway + and !railway + and ( + older today -2 years + or ${LAST_CHECK_DATE_KEYS.joinToString(" or ") { "$it < today -2 years" }} + ) + and (name or brand or noname = yes or name:signed = no) """).toElementFilterExpression() } override val changesetComment = "Survey if places (shops and other shop-like) still exist" @@ -49,15 +47,13 @@ class CheckShopExistence( override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.filter { isApplicableTo(it) } - override fun isApplicableTo(element: Element): Boolean { - if (!filter.matches(element)) return false - val tags = element.tags - // only show places that can be named somehow - return hasName(tags) - } + override fun isApplicableTo(element: Element): Boolean = + filter.matches(element) && + element.isPlace() && + hasName(element) override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = CheckShopExistenceForm() @@ -65,10 +61,10 @@ class CheckShopExistence( tags.updateCheckDate() } - private fun hasName(tags: Map) = hasProperName(tags) || hasFeatureName(tags) + private fun hasName(element: Element) = hasProperName(element.tags) || hasFeatureName(element) private fun hasProperName(tags: Map): Boolean = tags.containsKey("name") || tags.containsKey("brand") - private fun hasFeatureName(tags: Map) = getFeature(tags) != null + private fun hasFeatureName(element: Element) = getFeature(element)?.name != null } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceForm.kt index 7ad3afa741..a533d8e31c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceForm.kt @@ -6,7 +6,7 @@ import de.westnordost.streetcomplete.quests.AnswerItem class CheckShopExistenceForm : AbstractOsmQuestForm() { override val buttonPanelAnswers = listOf( - AnswerItem(R.string.quest_generic_hasFeature_no) { replaceShop() }, + AnswerItem(R.string.quest_generic_hasFeature_no) { replacePlace() }, AnswerItem(R.string.quest_generic_hasFeature_yes) { applyAnswer(Unit) }, ) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopType.kt index 9415162e33..3abb313fcc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopType.kt @@ -5,45 +5,41 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_EXPRESSION -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.LAST_CHECK_DATE_KEYS import de.westnordost.streetcomplete.osm.Tags -import de.westnordost.streetcomplete.osm.isShopExpressionFragment -import de.westnordost.streetcomplete.osm.replaceShop +import de.westnordost.streetcomplete.osm.isDisusedPlace +import de.westnordost.streetcomplete.osm.isPlace +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop +import de.westnordost.streetcomplete.osm.replacePlace import de.westnordost.streetcomplete.osm.updateCheckDate class CheckShopType : OsmElementQuestType { - private val disusedShopsFilter by lazy { """ - nodes, ways with ( - shop = vacant - or office = vacant - or (${isShopExpressionFragment("disused")} - and !man_made - and !historic - and !military - and !power - and !tourism - and !attraction - and !amenity - and !leisure - and !aeroway - and !railway - and !craft - and !healthcare - and !office - and !shop + private val filter by lazy { """ + nodes, ways with + !man_made + and !historic + and !military + and !power + and !tourism + and !attraction + and !amenity + and !leisure + and !aeroway + and !railway + and !craft + and !healthcare + and (!office or office = vacant) + and (!shop or shop = vacant) + and ( + older today -1 years + or ${LAST_CHECK_DATE_KEYS.joinToString(" or ") { "$it < today -1 years" }} ) - ) and ( - older today -1 years - or ${LAST_CHECK_DATE_KEYS.joinToString(" or ") { "$it < today -1 years" }} - ) """.toElementFilterExpression() } + override val changesetComment = "Survey if vacant shops are still vacant" override val wikiLink = "Key:disused:" override val icon = R.drawable.ic_quest_check_shop @@ -55,14 +51,15 @@ class CheckShopType : OsmElementQuestType { mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element): Boolean = - disusedShopsFilter.matches(element) + element.isDisusedPlace() && + filter.matches(element) && /* elements tagged like "shop=ice_cream + disused:amenity=bank" should not appear as quests. * This is arguably a tagging mistake, but that mistake should not lead to all the tags of * this element being cleared when the quest is answered */ - && !IS_SHOP_EXPRESSION.matches(element) + !element.isPlace() override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = ShopTypeForm() @@ -72,7 +69,7 @@ class CheckShopType : OsmElementQuestType { tags.updateCheckDate() } is ShopType -> { - tags.replaceShop(answer.tags) + tags.replacePlace(answer.tags) } } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopGoneDialog.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopGoneDialog.kt index efc62f59ac..64f8e32db1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopGoneDialog.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopGoneDialog.kt @@ -14,7 +14,8 @@ import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.databinding.DialogShopGoneBinding import de.westnordost.streetcomplete.databinding.ViewShopTypeBinding -import de.westnordost.streetcomplete.osm.IS_SHOP_EXPRESSION +import de.westnordost.streetcomplete.osm.POPULAR_PLACE_FEATURE_IDS +import de.westnordost.streetcomplete.osm.isPlace import de.westnordost.streetcomplete.view.controller.FeatureViewController import de.westnordost.streetcomplete.view.dialogs.SearchFeaturesDialog @@ -56,6 +57,7 @@ class ShopGoneDialog( featureCtrl.feature?.name, ::filterOnlyShops, ::onSelectedFeature, + POPULAR_PLACE_FEATURE_IDS, true ).show() } @@ -74,7 +76,7 @@ class ShopGoneDialog( private fun filterOnlyShops(feature: Feature): Boolean { val fakeElement = Node(-1L, LatLon(0.0, 0.0), feature.tags, 0) - return IS_SHOP_EXPRESSION.matches(fakeElement) + return fakeElement.isPlace() } private fun onSelectedFeature(feature: Feature) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopTypeForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopTypeForm.kt index 97b7d0a07f..34de4351e5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopTypeForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/ShopTypeForm.kt @@ -8,7 +8,8 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.databinding.ViewShopTypeBinding -import de.westnordost.streetcomplete.osm.IS_SHOP_EXPRESSION +import de.westnordost.streetcomplete.osm.POPULAR_PLACE_FEATURE_IDS +import de.westnordost.streetcomplete.osm.isPlace import de.westnordost.streetcomplete.quests.AbstractOsmQuestForm import de.westnordost.streetcomplete.util.ktx.geometryType import de.westnordost.streetcomplete.view.controller.FeatureViewController @@ -45,14 +46,15 @@ class ShopTypeForm : AbstractOsmQuestForm() { countryOrSubdivisionCode, featureCtrl.feature?.name, ::filterOnlyShops, - ::onSelectedFeature + ::onSelectedFeature, + POPULAR_PLACE_FEATURE_IDS, ).show() } } private fun filterOnlyShops(feature: Feature): Boolean { val fakeElement = Node(-1L, LatLon(0.0, 0.0), feature.tags, 0) - return IS_SHOP_EXPRESSION.matches(fakeElement) + return fakeElement.isPlace() } private fun onSelectedFeature(feature: Feature) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/SpecifyShopType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/SpecifyShopType.kt index c2eb258555..752fef7c31 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/SpecifyShopType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/shop_type/SpecifyShopType.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.removeCheckDates class SpecifyShopType : OsmFilterQuestType() { @@ -34,13 +33,13 @@ class SpecifyShopType : OsmFilterQuestType() { override val changesetComment = "Survey shop types" override val wikiLink = "Key:shop" override val icon = R.drawable.ic_quest_shop - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override fun getTitle(tags: Map) = R.string.quest_shop_type_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = ShopTypeForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/smoking/AddSmoking.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/smoking/AddSmoking.kt index f515b52062..ac101c1228 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/smoking/AddSmoking.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/smoking/AddSmoking.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop import de.westnordost.streetcomplete.osm.updateWithCheckDate class AddSmoking : OsmFilterQuestType() { @@ -42,14 +41,14 @@ class AddSmoking : OsmFilterQuestType() { override val changesetComment = "Survey whether smoking is allowed or prohibited" override val wikiLink = "Key:smoking" override val icon = R.drawable.ic_quest_smoking - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(CITIZEN) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside_regional_warning override fun getTitle(tags: Map) = R.string.quest_smoking_title2 override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = SmokingAllowedForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt index 28a1cc619e..907b554e1a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt @@ -4,11 +4,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.filter import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.WHEELCHAIR -import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.osm.isPlaceOrDisusedShop class AddWheelchairAccessBusiness : OsmFilterQuestType() { @@ -111,14 +110,14 @@ class AddWheelchairAccessBusiness : OsmFilterQuestType() { override val changesetComment = "Survey wheelchair accessibility of places" override val wikiLink = "Key:wheelchair" override val icon = R.drawable.ic_quest_wheelchair_shop - override val isReplaceShopEnabled = true + override val isReplacePlaceEnabled = true override val achievements = listOf(WHEELCHAIR) override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside override fun getTitle(tags: Map) = R.string.quest_wheelchairAccess_outside_title override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) = - getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION) + getMapData().asSequence().filter { it.isPlaceOrDisusedShop() } override fun createForm() = AddWheelchairAccessBusinessForm() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt index f54ed4697b..b0d5214284 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt @@ -30,7 +30,7 @@ class AddWheelchairAccessToiletsPart : OsmFilterQuestType): Int? { - val icon = featureDictionary.getFeature(tags)?.let { presetIconIndex[it.icon] } +@DrawableRes fun getPinIcon(featureDictionary: FeatureDictionary, element: Element): Int? { + val icon = featureDictionary.getFeature(element)?.let { presetIconIndex[it.icon] } if (icon != null) return icon - if (getShortHouseNumber(tags) != null && getNameLabel(tags) == null) { + if (getShortHouseNumber(element.tags) != null && getNameLabel(element.tags) == null) { return R.drawable.ic_none } diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/DummyFeature.kt b/app/src/main/java/de/westnordost/streetcomplete/util/DummyFeature.kt similarity index 95% rename from app/src/main/java/de/westnordost/streetcomplete/overlays/shops/DummyFeature.kt rename to app/src/main/java/de/westnordost/streetcomplete/util/DummyFeature.kt index f9def2b16b..8c19a33da9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/shops/DummyFeature.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/DummyFeature.kt @@ -1,4 +1,4 @@ -package de.westnordost.streetcomplete.overlays.shops +package de.westnordost.streetcomplete.util import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.GeometryType diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/NameAndLocationLabel.kt b/app/src/main/java/de/westnordost/streetcomplete/util/NameAndLocationLabel.kt index bc68925dcc..ba8935c617 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/NameAndLocationLabel.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/NameAndLocationLabel.kt @@ -1,16 +1,13 @@ package de.westnordost.streetcomplete.util -import android.content.res.Configuration import android.content.res.Resources import android.text.Html import androidx.core.text.parseAsHtml import de.westnordost.osmfeatures.FeatureDictionary -import de.westnordost.osmfeatures.GeometryType import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.mapdata.Element -import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.osm.ALL_ROADS -import de.westnordost.streetcomplete.util.ktx.geometryType +import de.westnordost.streetcomplete.util.ktx.getFeature import java.util.Locale fun getNameAndLocationLabel( @@ -19,9 +16,10 @@ fun getNameAndLocationLabel( featureDictionary: FeatureDictionary?, showHouseNumber: Boolean? = null ): CharSequence? { - // only if geometry is not a node because at this point we cannot tell apart points vs vertices - val geometryType = if (element.type == ElementType.NODE) null else element.geometryType - val feature = featureDictionary?.getFeatureName(resources.configuration, element.tags, geometryType) + val locales = getLocalesForFeatureDictionary(resources.configuration) + val feature = featureDictionary + ?.getFeature(element, locales) + ?.name ?.withNonBreakingSpaces() ?.inItalics() val name = getNameLabel(element.tags) @@ -32,7 +30,7 @@ fun getNameAndLocationLabel( val featureEx = if (taxon != null && feature != null) { resources.getString(R.string.label_feature_taxon, feature, taxon) } else { - feature + feature ?: taxon } val nameAndFeatureName = if (name != null && featureEx != null) { @@ -81,20 +79,6 @@ private fun getLocationHtml( } } -/** Returns the feature name only, e.g. "Bakery" */ -fun FeatureDictionary.getFeatureName( - configuration: Configuration, - tags: Map, - geometryType: GeometryType? = null, -): String? = this - .byTags(tags) - .isSuggestion(false) - .forLocale(*getLocalesForFeatureDictionary(configuration)) - .forGeometry(geometryType) - .find() - .firstOrNull() - ?.name - /** Returns the taxon of a tree or null if unknown */ fun getTreeTaxon(tags: Map, languageTag: String): String? { if (tags["natural"] != "tree") return null diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/ktx/FeatureDictionary.kt b/app/src/main/java/de/westnordost/streetcomplete/util/ktx/FeatureDictionary.kt index 45bafb2d29..dd6444549d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/ktx/FeatureDictionary.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/ktx/FeatureDictionary.kt @@ -2,8 +2,20 @@ package de.westnordost.streetcomplete.util.ktx import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.FeatureDictionary +import de.westnordost.streetcomplete.data.osm.mapdata.Element +import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import java.util.Locale -fun FeatureDictionary.getFeature(tags: Map): Feature? = - this.byTags(tags) +fun FeatureDictionary.getFeature( + element: Element, + locales: Array? = null, +): Feature? { + // only if geometry is not a node because at this point we cannot tell apart points vs vertices + val geometryType = if (element.type == ElementType.NODE) null else element.geometryType + val builder = this + .byTags(element.tags) .isSuggestion(false) // no brands - .find().firstOrNull() + .forGeometry(geometryType) + if (locales != null) builder.forLocale(*locales) + return builder.find().firstOrNull() +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/ktx/Map.kt b/app/src/main/java/de/westnordost/streetcomplete/util/ktx/Map.kt new file mode 100644 index 0000000000..98a6e9a99d --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/util/ktx/Map.kt @@ -0,0 +1,3 @@ +package de.westnordost.streetcomplete.util.ktx + +fun Map.containsAll(other: Map) = other.all { this[it.key] == it.value } diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/controller/FeatureViewController.kt b/app/src/main/java/de/westnordost/streetcomplete/view/controller/FeatureViewController.kt index 26fcaebfaa..f413825afb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/view/controller/FeatureViewController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/view/controller/FeatureViewController.kt @@ -130,7 +130,6 @@ private fun Feature.findMatchedName(searchText: String): String? { } private fun Feature.getIconDrawable(context: Context): Drawable? { - if (icon == null) return null - val id = presetIconIndex[icon] ?: return null + val id = icon?.let { presetIconIndex[it] } ?: R.drawable.ic_preset_maki_marker_stroked return context.getDrawable(id) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/dialogs/SearchFeaturesDialog.kt b/app/src/main/java/de/westnordost/streetcomplete/view/dialogs/SearchFeaturesDialog.kt index 2b31bab68f..acc621deab 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/view/dialogs/SearchFeaturesDialog.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/view/dialogs/SearchFeaturesDialog.kt @@ -29,6 +29,7 @@ class SearchFeaturesDialog( text: String? = null, private val filterFn: (Feature) -> Boolean = { true }, private val onSelectedFeatureFn: (Feature) -> Unit, + private val codesOfDefaultFeatures: List, private val dismissKeyboardOnClose: Boolean = false, ) : AlertDialog(context) { @@ -39,17 +40,7 @@ class SearchFeaturesDialog( private val searchText: String? get() = binding.searchEditText.nonBlankTextOrNull private val defaultFeatures: List 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) diff --git a/app/src/main/res/drawable/ic_quest_plus.xml b/app/src/main/res/drawable/ic_quest_dot.xml similarity index 67% rename from app/src/main/res/drawable/ic_quest_plus.xml rename to app/src/main/res/drawable/ic_quest_dot.xml index 2a486c760d..1b853bbb12 100644 --- a/app/src/main/res/drawable/ic_quest_plus.xml +++ b/app/src/main/res/drawable/ic_quest_dot.xml @@ -5,12 +5,14 @@ android:viewportHeight="128"> diff --git a/app/src/main/res/layout/fragment_overlay_things.xml b/app/src/main/res/layout/fragment_overlay_things.xml new file mode 100644 index 0000000000..c474486283 --- /dev/null +++ b/app/src/main/res/layout/fragment_overlay_things.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f1911f4a6..3b4b7500eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1687,7 +1687,7 @@ Partially means that a wheelchair can enter and use the restroom, but no handrai Buildings - Shops + Places Name: Other kind of place @@ -1696,6 +1696,10 @@ Partially means that a wheelchair can enter and use the restroom, but no handrai "Different place now" "Still same place" + Things + disused + Unknown object + Street parking diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/ShopKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/PlaceKtTest.kt similarity index 98% rename from app/src/test/java/de/westnordost/streetcomplete/osm/ShopKtTest.kt rename to app/src/test/java/de/westnordost/streetcomplete/osm/PlaceKtTest.kt index 1cb4fec4c6..fbccce43c0 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/ShopKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/PlaceKtTest.kt @@ -7,7 +7,7 @@ import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryDe import kotlin.test.Test import kotlin.test.assertEquals -class ShopKtTest { +class PlaceKtTest { @Test fun `replaceShop removes all previous survey keys`() { assertEquals( @@ -68,6 +68,6 @@ class ShopKtTest { private fun replaceShopApplied(newTags: Map, oldTags: Map): Set { val cb = StringMapChangesBuilder(oldTags) - cb.replaceShop(newTags) + cb.replacePlace(newTags) return cb.create().changes } diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/ThingsKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/ThingsKtTest.kt new file mode 100644 index 0000000000..9e2ed6ea42 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/ThingsKtTest.kt @@ -0,0 +1,34 @@ +package de.westnordost.streetcomplete.osm + +import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.data.osm.mapdata.Node +import kotlin.test.* +import kotlin.test.Test + +class ThingsKtTest { + + @Test fun `disused bench matches`() { + val fakeElement = Node(-1L, LatLon(0.0, 0.0), mapOf("disused:amenity" to "bench"), 0) + assertEquals(true, fakeElement.isDisusedThing()) + assertEquals(false, fakeElement.isThing()) + } + + @Test fun `flagpole matches`() { + val fakeElement = Node(-1L, LatLon(0.0, 0.0), mapOf("man_made" to "flagpole"), 0) + assertEquals(true, fakeElement.isThing()) + } + + @Test fun `specific flagpole matches`() { + // note that in search currently you may need to type "PL - Poland" + val fakeElement = Node(-1L, LatLon(0.0, 0.0), mapOf( + "country" to "PL", + "flag:name" to "Poland", + "flag:type" to "national", + "flag:wikidata" to "Q42436", + "man_made" to "flagpole", + "subject" to "Poland", + "subject:wikidata" to "Q36", + ), 0) + assertEquals(true, fakeElement.isThing()) + } +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt index efb07c7353..9481c073d9 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt @@ -8,8 +8,8 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class CheckExistenceTest { - private val questType = CheckExistence { tags -> - if (tags["amenity"] == "telephone") mock() else null + private val questType = CheckExistence { element -> + if (element.tags["amenity"] == "telephone") mock() else null } @Test fun `isApplicableTo returns false for known places with recently edited amenity=telephone`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt index 5e1583d0c3..467ef8a239 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt @@ -7,8 +7,8 @@ import kotlin.test.Test import kotlin.test.assertEquals class CheckShopExistenceTest { - private val questType = CheckShopExistence { tags -> - if (tags["shop"] == "greengrocer") mock() else null + private val questType = CheckShopExistence { element -> + if (element.tags["shop"] == "greengrocer") mock() else null } @Test diff --git a/res/graphics/quest/dot.svg b/res/graphics/quest/dot.svg new file mode 100644 index 0000000000..005d4ebded --- /dev/null +++ b/res/graphics/quest/dot.svg @@ -0,0 +1,2 @@ + + diff --git a/res/graphics/quest/plus.svg b/res/graphics/quest/plus.svg deleted file mode 100644 index be3fb4beca..0000000000 --- a/res/graphics/quest/plus.svg +++ /dev/null @@ -1,2 +0,0 @@ - -