diff --git a/assets/layers/arcade/arcade.json b/assets/layers/arcade/arcade.json new file mode 100644 index 000000000..2ab70ba65 --- /dev/null +++ b/assets/layers/arcade/arcade.json @@ -0,0 +1,115 @@ +{ + "id": "arcade", + "name": { + "en": "Arcades" + }, + "description": { + "en": "Layer showing arcades" + }, + "source": { + "osmTags": "leisure=amusement_arcade" + }, + "minzoom": 10, + "title": { + "render": { + "en": "Arcade" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "*": "{name}" + } + } + ] + }, + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "square", + "color": "white" + }, + { + "icon": "./assets/layers/arcade/arcade.svg" + } + ] + } + ], + "lineRendering": [ + { + "width": 3, + "color": "#0e8517" + } + ], + "presets": [ + { + "title": { + "en": "an arcade" + }, + "tags": [ + "leisure=amusement_arcade" + ] + } + ], + "tagRenderings": [ + "images", + "reviews", + { + "builtin": "name", + "override": { + "question": { + "en": "What is the name of this arcade?" + }, + "render": { + "en": "This arcade is called {name}" + } + } + }, + { + "id": "virtual_reality", + "question": { + "en": "Does this arcade offer virtual-reality gaming?" + }, + "mappings": [ + { + "if": "virtual_reality=yes", + "then": { + "en": "This arcade offers virtual-reality gaming." + } + }, + { + "if": "virtual_reality=only", + "then": { + "en": "This arcade only offers virtual-reality gaming." + } + }, + { + "if": "virtual_reality=", + "then": { + "en": "This arcade doesn't offer virtual-reality gaming" + } + } + ] + }, + "brand", + "opening_hours", + "website", + "email", + "phone", + "payment-options", + "level", + "description", + "toilet_at_amenity_lib.all" + ], + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": true + }, + "credits": "Robin van der Linde", + "credits:uid": 5093765 +} \ No newline at end of file diff --git a/assets/layers/arcade/arcade.svg b/assets/layers/arcade/arcade.svg new file mode 100644 index 000000000..5f4031683 --- /dev/null +++ b/assets/layers/arcade/arcade.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/layers/arcade/arcade.svg.license b/assets/layers/arcade/arcade.svg.license new file mode 100644 index 000000000..ff6ee9492 --- /dev/null +++ b/assets/layers/arcade/arcade.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: meased +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/arcade/license_info.json b/assets/layers/arcade/license_info.json new file mode 100644 index 000000000..f82b95ae4 --- /dev/null +++ b/assets/layers/arcade/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "arcade.svg", + "license": "CC0-1.0", + "authors": [ + "meased" + ], + "sources": [ + "https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/leisure/amusement_arcade.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/campsite/campsite.json b/assets/layers/campsite/campsite.json index 5e9024b87..d29e848c4 100644 --- a/assets/layers/campsite/campsite.json +++ b/assets/layers/campsite/campsite.json @@ -280,7 +280,6 @@ ] } }, - "caravansites.caravansites-toilets", "toilet_at_amenity_lib.all", "questions", "mastodon" diff --git a/assets/layers/caravansites/caravansites.json b/assets/layers/caravansites/caravansites.json index 373de01a3..4240ceb99 100644 --- a/assets/layers/caravansites/caravansites.json +++ b/assets/layers/caravansites/caravansites.json @@ -666,85 +666,7 @@ ] } }, - { - "id": "caravansites-toilets", - "question": { - "en": "Does this place have toilets?", - "ca": "Aquest lloc té lavabos?", - "cs": "Má toto místo toalety?", - "da": "Har dette sted toiletter?", - "de": "Verfügt dieser Ort über Toiletten?", - "es": "¿Este lugar tiene baños?", - "fr": "Y-a-t’il des toilettes sur le site ?", - "hu": "Van-e itt WC?", - "it": "Questo posto ha servizi igienici?", - "ja": "ここにトイレはありますか?", - "nb_NO": "Har dette stedet toaletter?", - "nl": "Heeft deze plaats toiletten?", - "pl": "Czy to miejsce ma toalety?", - "pt": "Este lugar tem casas de banho?", - "pt_BR": "Este lugar tem banheiros?", - "ru": "Здесь есть туалеты?", - "zh_Hant": "這個地方有廁所嗎?" - }, - "mappings": [ - { - "if": { - "and": [ - "toilets=yes" - ] - }, - "then": { - "en": "This place has toilets", - "ca": "Aquest lloc té lavabos", - "cs": "Toto místo má toalety", - "da": "Dette sted har toiletter", - "de": "Dieser Ort verfügt über Toiletten", - "es": "Este lugar tiene baños", - "fr": "Ce site a des toilettes", - "hu": "Itt van WC", - "id": "Tempat sini ada tandas", - "it": "Questo posto ha servizi igienici", - "ja": "ここにはトイレがある", - "nb_NO": "Dette stedet har toalettfasiliteter", - "nl": "Deze plaats heeft toiletten", - "pl": "To miejsce ma toalety", - "pt": "Este lugar tem casa de banho", - "pt_BR": "Este lugar tem banheiros", - "ru": "В этом месте есть туалеты", - "zh_Hant": "這個地方有廁所" - } - }, - { - "if": { - "and": [ - "toilets=no" - ] - }, - "then": { - "en": "This place does not have toilets", - "ca": "Aquest lloc no té lavabos", - "cs": "Toto místo nemá toalety", - "da": "Dette sted har ikke toiletter", - "de": "Dieser Ort verfügt nicht über Toiletten", - "es": "Este lugar no tiene baños", - "eu": "Toki honek ez dauka komunik", - "fr": "Ce site n’a pas de toilettes", - "hu": "Itt nincs WC", - "id": "Tempat sini tiada tandas", - "it": "Questo posto non ha servizi igienici", - "ja": "ここにはトイレがない", - "nb_NO": "Dette stedet har ikke toalettfasiliteter", - "nl": "Deze plaats heeft geen toiletten", - "pl": "To miejsce nie ma toalet", - "pt": "Este lugar não tem casas de banho", - "pt_BR": "Este lugar não tem banheiros", - "ru": "В этом месте нет туалетов", - "zh_Hant": "這個地方並沒有廁所" - } - } - ] - }, + "has_toilets", { "id": "caravansites-website", "question": { diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 67556c5e1..f58d3333b 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -571,6 +571,28 @@ } } ] + }, + { + "id": "shelter", + "options": [ + { + "osmTags": { + "or": [ + "shelter=yes", + "shelter=separate" + ] + }, + "question": { + "en": "With a shelter", + "ca": "Amb refugi", + "cs": "S přístřeškem", + "de": "Mit Unterstand", + "es": "Con refugio", + "fr": "Avec un abri", + "it": "Con una pensilina" + } + } + ] } ], "allowMove": false diff --git a/assets/layers/hut/alpine_hut.svg b/assets/layers/hut/alpine_hut.svg new file mode 100644 index 000000000..e790edd62 --- /dev/null +++ b/assets/layers/hut/alpine_hut.svg @@ -0,0 +1,15 @@ + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/assets/layers/hut/alpine_hut.svg.license b/assets/layers/hut/alpine_hut.svg.license new file mode 100644 index 000000000..5e3616ed7 --- /dev/null +++ b/assets/layers/hut/alpine_hut.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Geozeisig +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json new file mode 100644 index 000000000..3d126ea6f --- /dev/null +++ b/assets/layers/hut/hut.json @@ -0,0 +1,179 @@ +{ + "id": "hut", + "name": { + "en": "Huts" + }, + "description": { + "en": "Layer showing basic huts, wilderness huts and alpine huts" + }, + "source": { + "osmTags": { + "or": [ + "tourism=wilderness_hut", + "tourism=alpine_hut", + { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + } + ] + } + }, + "minzoom": 10, + "title": { + "render": "Hut", + "mappings": [ + { + "if": "name~*", + "then": "{name}" + }, + { + "if": "tourism=wilderness_hut", + "then": "wilderness hut" + }, + { + "if": "tourism=alpine_hut", + "then": "alpine hut" + }, + { + "if": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + }, + "then": "basic hut" + } + ] + }, + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": { + "render": "./assets/layers/shelter/shelter.svg", + "mappings": [ + { + "if": "tourism=wilderness_hut", + "then": "./assets/layers/hut/wilderness_hut.svg" + }, + { + "if": "tourism=alpine_hut", + "then": "./assets/layers/hut/alpine_hut.svg" + }, + { + "if": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + }, + "then": "./assets/layers/shelter/shelter.svg" + } + ] + } + } + ] + } + ], + "lineRendering": [], + "presets": [ + { + "tags": [ + "tourism=wilderness_hut" + ], + "title": { + "en": "wilderness hut" + }, + "description": { + "en": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas and a fireplace or stove for heating and cooking." + } + }, + { + "tags": [ + "tourism=alpine_hut" + ], + "title": { + "en": "alpine hut" + }, + "description": { + "en": "A serviced remote building located in the mountains intended to provide board and lodging." + } + }, + { + "tags": [ + "amenity=shelter", + "shelter_type=basic_hut" + ], + "title": { + "en": "basic hut" + }, + "description": { + "en": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas without a fireplace or stove." + } + } + ], + "tagRenderings": [ + "images", + "name", + { + "builtin": "website", + "override": { + "condition": "tourism=wilderness_hut", + "id": "website-single" + } + }, + { + "builtin": "contact", + "override": { + "condition": "tourism=alpine_hut" + } + }, + "reservation", + "caravansites.caravansites-fee", + { + "id": "drinking_water", + "question": { + "en": "Is drinking water available here?" + }, + "mappings": [ + { + "if": "drinking_water=yes", + "then": { + "en": "Here is drinking water available." + } + }, + { + "if": "drinking_water=no", + "then": { + "en": "Here is no drinking water available." + } + } + ] + }, + "has_toilets", + "description", + { + "id": "preset_type", + "render": "{preset_type_select()}" + }, + { + "builtin": "shelter.shelter-type", + "override": { + "condition": "amenity=shelter" + } + } + ], + "filter":[ + "free" + ], + "allowMove": { + "enableRelocation": false, + "enableImproveAccuracy": true + } +} diff --git a/assets/layers/hut/license_info.json b/assets/layers/hut/license_info.json new file mode 100644 index 000000000..f8341f4f8 --- /dev/null +++ b/assets/layers/hut/license_info.json @@ -0,0 +1,22 @@ +[ + { + "path": "alpine_hut.svg", + "license": "CC0-1.0", + "authors": [ + "Geozeisig" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Alpinehut.svg" + ] + }, + { + "path": "wilderness_hut.svg", + "license": "CC0-1.0", + "authors": [ + "Geozeisig" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Wilderness_hut.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/hut/wilderness_hut.svg b/assets/layers/hut/wilderness_hut.svg new file mode 100644 index 000000000..cbf146077 --- /dev/null +++ b/assets/layers/hut/wilderness_hut.svg @@ -0,0 +1,15 @@ + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/assets/layers/hut/wilderness_hut.svg.license b/assets/layers/hut/wilderness_hut.svg.license new file mode 100644 index 000000000..5e3616ed7 --- /dev/null +++ b/assets/layers/hut/wilderness_hut.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Geozeisig +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/parking_spaces/parking_spaces.json b/assets/layers/parking_spaces/parking_spaces.json index 64e5f907e..0c36e859b 100644 --- a/assets/layers/parking_spaces/parking_spaces.json +++ b/assets/layers/parking_spaces/parking_spaces.json @@ -69,6 +69,15 @@ "icon": { "render": "./assets/layers/parking_spaces/parking_space.svg", "mappings": [ + { + "if": { + "or": [ + "access=private", + "access=no" + ] + }, + "then": "./assets/layers/parking_spaces/parking_space_private.svg" + }, { "if": "parking_space=disabled", "then": "./assets/layers/toilet/wheelchair.svg" @@ -99,7 +108,7 @@ ], "lineRendering": [ { - "color": "#696969", + "color": "dimgray", "width": "1" } ], @@ -295,6 +304,69 @@ "it": "Questo è un posto auto riservato al car sharing.", "nl": "Deze parkeerplek is gereserveerd voor autodelen." } + }, + { + "if": "parking_space=women", + "then": { + "en": "This is a parking space reserved for women.", + "nl": "Deze parkeerplek is gereserveerd voor vrouwen." + } + } + ] + }, + { + "id": "access", + "question": { + "en": "Who can use this parking space?", + "nl": "Wie mag deze parkeerplek gebruiken?" + }, + "render": { + "en": "Access of parking space: {access}", + "nl": "Toegang tot parkeerplek: {access}" + }, + "freeform": { + "key": "access", + "type": "string", + "addExtraTags": [ + "fixme=Freeform used on 'access'-tag: possibly a wrong value" + ] + }, + "mappings": [ + { + "if": "access=", + "then": { + "en": "Anyone can use this parking space.", + "nl": "Iedereen kan deze parkeerplek gebruiken." + }, + "hideInAnswer": true + }, + { + "if": "access=yes", + "then": { + "en": "Anyone can use this parking space.", + "nl": "Iedereen kan deze parkeerplek gebruiken." + } + }, + { + "if": "access=customers", + "then": { + "en": "This parking space is reserved for customers.", + "nl": "Deze parkeerplek is gereserveerd voor klanten." + } + }, + { + "if": "access=private", + "then": { + "en": "This parking space is private and cannot be used by the general public.", + "nl": "Deze parkeerplek is privé en mag niet door het grote publiek worden gebruikt." + } + }, + { + "if": "access=permit", + "then": { + "en": "This parking space is reserved for permit holders.", + "nl": "Deze parkeerplek is gereserveerd voor vergunninghouders." + } } ] }, @@ -310,6 +382,19 @@ "nl": "Deze parkeerplek heeft {capacity} plaatsen." }, "mappings": [ + { + "if": "capacity=", + "then": { + "en": "This parking space has 1 space.", + "ca": "Aquest espai d'aparcament té 1 plaça.", + "cs": "Toto parkoviště má 1 místo.", + "de": "Dieser Parkplatz hat 1 Stellplatz.", + "es": "Esta plaza de aparcamiento tiene 1 plaza.", + "it": "Questo posto auto ha 1 spazio.", + "nl": "Deze parkeerplek heeft 1 plaats." + }, + "hideInAnswer": true + }, { "if": "capacity=1", "then": { @@ -329,4 +414,4 @@ "enableImproveAccuracy": true, "enableRelocation": false } -} +} \ No newline at end of file diff --git a/assets/layers/picnic_site/picnic_site.json b/assets/layers/picnic_site/picnic_site.json new file mode 100644 index 000000000..ef02be70b --- /dev/null +++ b/assets/layers/picnic_site/picnic_site.json @@ -0,0 +1,304 @@ +{ + "id": "picnic_site", + "name": { + "en": "Picnic sites", + "nl": "Picknickplaatsen" + }, + "description": { + "en": "Picnic sites for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "nl": "Picknickplaatsen voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen" + }, + "source": { + "osmTags": "tourism=picnic_site" + }, + "minzoom": 10, + "title": { + "render": { + "en": "Picnic site", + "nl": "Picknickplaats" + } + }, + "pointRendering": [ + { + "iconSize": "35,35", + "location": [ + "point", + "centroid" + ], + "anchor": "center", + "marker": [ + { + "color": "#3984e6", + "icon": "circle" + }, + { + "icon": "./assets/layers/picnic_table/picnic_table.svg" + } + ] + } + ], + "lineRendering": [ + { + "color": "#3984e6", + "fillColor": "#3984e6bd", + "width": 5 + } + ], + "presets": [ + { + "tags": [ + "tourism=picnic_site" + ], + "title": { + "en": "a picnic site", + "nl": "een picknickplaats" + }, + "description": { + "en": "A picnic site for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "nl": "Een picknickplaats voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen" + } + } + ], + "tagRenderings": [ + "images", + { + "builtin": "name", + "override": { + "render": { + "en": "This picnic site is called {name}", + "nl": "Deze picknickplaats heet {name}" + } + } + }, + { + "id": "shelter", + "question": { + "en": "Does this picnic site have a shelter?", + "nl": "Heeft deze picknickplaats een schuilplaats?" + }, + "mappings": [ + { + "if": "shelter=yes", + "then": { + "en": "This picnic site has a shelter.", + "nl": "Deze picknickplaats heeft een schuilplaats." + } + }, + { + "if": "shelter=no", + "then": { + "en": "This picnic site does not have a shelter.", + "nl": "Deze picknickplaats heeft geen schuilplaats." + } + }, + { + "if": "shelter=separate", + "then": { + "en": "This picnic site has a shelter, but is is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een schuilplaats, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "fireplace", + "question": { + "en": "Does this picnic site have a firepit?", + "nl": "Heeft deze picknickplaats een vuurplaats?" + }, + "mappings": [ + { + "if": "fireplace=yes", + "then": { + "en": "This picnic site has a firepit.", + "nl": "Deze picknickplaats heeft een vuurplaats." + } + }, + { + "if": "fireplace=no", + "then": { + "en": "This picnic site does not have a firepit.", + "nl": "Deze picknickplaats heeft geen vuurplaats." + } + }, + { + "if": "fireplace=separate", + "then": { + "en": "This picnic site has a firepit, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een vuurplaats, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "bbq", + "question": { + "en": "Does this picnic site have a BBQ?", + "nl": "Heeft deze picknickplaats een BBQ?" + }, + "mappings": [ + { + "if": "bbq=yes", + "then": { + "en": "This picnic site has a BBQ.", + "nl": "Deze picknickplaats heeft een BBQ." + } + }, + { + "if": "bbq=no", + "then": { + "en": "This picnic site does not have a BBQ.", + "nl": "Deze picknickplaats heeft geen BBQ." + } + }, + { + "if": "bbq=separate", + "then": { + "en": "This picnic site has a BBQ, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een BBQ, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "covered", + "question": { + "en": "Is this picnic site covered?", + "nl": "Is deze picknickplaats overdekt?" + }, + "mappings": [ + { + "if": "covered=yes", + "then": { + "en": "This picnic site is covered.", + "nl": "Deze picknickplaats is overdekt." + } + }, + { + "if": "covered=no", + "then": { + "en": "This picnic site is not covered.", + "nl": "Deze picknickplaats is niet overdekt." + } + } + ] + }, + { + "id": "drinking_water", + "question": { + "en": "Does this picnic site have drinking water?", + "nl": "Heeft deze picknickplaats drinkwater?" + }, + "mappings": [ + { + "if": "drinking_water=yes", + "then": { + "en": "This picnic site has drinking water.", + "nl": "Deze picknickplaats heeft drinkwater." + } + }, + { + "if": "drinking_water=no", + "then": { + "en": "This picnic site does not have drinking water.", + "nl": "Deze picknickplaats heeft geen drinkwater." + } + }, + { + "if": "drinking_water=separate", + "then": { + "en": "This picnic site has drinking water, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft drinkwater, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "openfire", + "question": { + "en": "Is open fire allowed at this picnic site?", + "nl": "Is open vuur toegestaan op deze picknickplaats?" + }, + "mappings": [ + { + "if": "openfire=yes", + "then": { + "en": "Open fire is allowed at this picnic site.", + "nl": "Open vuur is toegestaan op deze picknickplaats." + } + }, + { + "if": "openfire=no", + "then": { + "en": "Open fire is not allowed at this picnic site.", + "nl": "Open vuur is niet toegestaan op deze picknickplaats." + } + }, + { + "if": "openfire=permit", + "then": { + "en": "Open fire is allowed at this picnic site with a permit.", + "nl": "Open vuur is toegestaan op deze picknickplaats met een vergunning." + } + } + ] + } + ], + "filter": [ + "shelter", + { + "id": "fireplace", + "options": [ + { + "question": { + "en": "With a firepit", + "nl": "Met een vuurplaats" + }, + "osmTags": { + "or": [ + "fireplace=yes", + "fireplace=separate" + ] + } + } + ] + }, + { + "id": "bbq", + "options": [ + { + "question": { + "en": "With a BBQ", + "nl": "Met een BBQ" + }, + "osmTags": { + "or": [ + "bbq=yes", + "bbq=separate" + ] + } + } + ] + }, + { + "id": "drinking_water", + "options": [ + { + "question": { + "en": "With drinking water", + "nl": "Met drinkwater" + }, + "osmTags": { + "or": [ + "drinking_water=yes", + "drinking_water=separate" + ] + } + } + ] + } + ], + "allowMove": { + "enableImproveAccuracy": true + } +} \ No newline at end of file diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index dd3949ccd..d33a86485 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3118,7 +3118,8 @@ "if": "nobrand=yes", "addExtraTags": [ "brand=", - "brand:wikidata=" + "brand:wikidata=", + "brand:wikipedia=" ], "then": { "en": "Not part of a bigger brand", @@ -3657,4 +3658,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 5b562d9be..a8c63e6ef 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -43,7 +43,8 @@ "craft=key_cutter" ] }, - "shop!=mall" + "shop!=mall", + "shop!=no" ] } }, @@ -486,7 +487,12 @@ "es": "Esta tienda no tiene una marca específica, no forma parte de una cadena más grande", "it": "Questo negozio non ha un marchio specifico, non fa parte di una catena più grande", "uk": "Цей магазин не має певного бренду, він не є частиною великої мережі" - } + }, + "addExtraTags": [ + "brand=", + "brand:wikidata=", + "brand:wikipedia=" + ] } ] }, diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 2ecaf1df5..e19ab6d52 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -87,6 +87,10 @@ "if": "camera:type=doorbell", "then": "./assets/layers/surveillance_camera/doorbell.svg" }, + { + "if": "camera:type=panorama", + "then": "./assets/themes/surveillance/panorama.svg" + }, { "if": "_direction:leftright=right", "then": "./assets/themes/surveillance/cam_right.svg" @@ -110,6 +114,10 @@ ] }, "then": "50,35,center" + }, + { + "if": "camera:type=panorama", + "then": "55,55,center" } ], "render": "35,35,center" @@ -407,6 +415,16 @@ "ru": "Панорамная камера" } }, + { + "if": "camera:type=panorama", + "icon": "./assets/themes/surveillance/panorama.svg", + "then": { + "en": "A 360° camera", + "de": "Eine 360°-Kamera", + "es": "Una cámara de 360°", + "fr": "Une caméra 360°" + } + }, { "if": "camera:type=doorbell", "icon": { @@ -530,7 +548,7 @@ "da": "Hvilken form for overvågning er dette kamera?", "de": "Was überwacht diese Kamera?", "es": "¿Qué tipo de vigilancia es esta cámara?", - "fr": "De quel genre de surveillance cette caméra est-elle ?", + "fr": "De quel genre de surveillance cette caméra est-elle ?", "it": "Che tipo di sorveglianza è questa telecamera?", "nl": "Wat soort bewaking wordt hier uitgevoerd?", "sl": "Kaj nadzoruje ta kamera?" diff --git a/assets/layers/transit_stops/transit_stops.json b/assets/layers/transit_stops/transit_stops.json index d9d045554..9874e52a7 100644 --- a/assets/layers/transit_stops/transit_stops.json +++ b/assets/layers/transit_stops/transit_stops.json @@ -548,28 +548,7 @@ } ], "filter": [ - { - "id": "shelter", - "options": [ - { - "osmTags": { - "or": [ - "shelter=yes", - "shelter=separate" - ] - }, - "question": { - "en": "With a shelter", - "ca": "Amb refugi", - "cs": "S přístřeškem", - "de": "Mit Unterstand", - "es": "Con refugio", - "fr": "Avec un abri", - "it": "Con una pensilina" - } - } - ] - }, + "shelter", { "id": "bench", "options": [ @@ -617,4 +596,4 @@ "tactile_paving" ], "allowMove": false -} +} \ No newline at end of file diff --git a/assets/themes/arcade/arcade.json b/assets/themes/arcade/arcade.json new file mode 100644 index 000000000..137cdf86a --- /dev/null +++ b/assets/themes/arcade/arcade.json @@ -0,0 +1,13 @@ +{ + "id": "arcade", + "title": { + "en": "Arcades" + }, + "description": { + "en": "A map of arcades" + }, + "icon": "./assets/layers/arcade/arcade.svg", + "layers": [ + "arcade" + ] +} \ No newline at end of file diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 5b19fa6de..80382fd51 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -38,6 +38,9 @@ "zh_Hant": "顯示由MapComplete進行的變動" }, "icon": "./assets/svg/logo.svg", + "startZoom": 1, + "startLat": 0, + "startLon": 0, "hideFromOverview": true, "layers": [ { diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 7158a1d5e..a91c126a1 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -60,12 +60,14 @@ "nature_reserve", { "builtin": [ + "hut", "shelter" ], "override": { "minzoom": 11 } }, + "picnic_site", { "builtin": [ "map", diff --git a/assets/themes/surveillance/license_info.json b/assets/themes/surveillance/license_info.json index 53a9f3c25..4b40d545c 100644 --- a/assets/themes/surveillance/license_info.json +++ b/assets/themes/surveillance/license_info.json @@ -30,5 +30,13 @@ "Pieter Vander Vennet" ], "sources": [] + }, + { + "path": "panorama.svg", + "license": "CC0-1.0", + "authors": [ + "Martin Bodin" + ], + "sources": [] } ] \ No newline at end of file diff --git a/assets/themes/surveillance/panorama.svg b/assets/themes/surveillance/panorama.svg new file mode 100644 index 000000000..6e135df24 --- /dev/null +++ b/assets/themes/surveillance/panorama.svg @@ -0,0 +1,109 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/themes/surveillance/panorama.svg.license b/assets/themes/surveillance/panorama.svg.license new file mode 100644 index 000000000..27a495574 --- /dev/null +++ b/assets/themes/surveillance/panorama.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Martin Bodin +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index 8099c232b..196b2de20 100644 --- a/langs/en.json +++ b/langs/en.json @@ -320,6 +320,7 @@ "menu": { "aboutCurrentThemeTitle": "About this map", "aboutMapComplete": "About MapComplete", + "downloadApp": "Download the app for Android", "filter": "Filter data", "legal": "Legal notices", "moreUtilsTitle": "Discover more", diff --git a/langs/layers/ca.json b/langs/layers/ca.json index e15f8fa0b..a68397a07 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -2468,17 +2468,6 @@ }, "question": "Aquest lloc té una estació d'abocament sanitari?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Aquest lloc té lavabos" - }, - "1": { - "then": "Aquest lloc no té lavabos" - } - }, - "question": "Aquest lloc té lavabos?" - }, "caravansites-website": { "question": "Aquest lloc té un lloc web?", "render": "Lloc web oficial: {website}" @@ -5510,6 +5499,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Amb refugi" + } + } + }, "3": { "options": { "0": { @@ -7996,6 +7992,9 @@ "mappings": { "0": { "then": "Aquest espai d'aparcament té 1 plaça." + }, + "1": { + "then": "Aquest espai d'aparcament té 1 plaça." } }, "render": "Aquests espais d'aparcament tenen {capacity} places." @@ -11556,7 +11555,7 @@ "2": { "then": "Una càmera panoràmica" }, - "3": { + "4": { "then": "Un timbre que es pot activar remotament en qualsevol moment o mitjançant la detecció de moviment. Aquests són típicament Smart, banderes connectades a Internet. Les marques típiques són Ring, Google Nest, Eufy, ..." } }, @@ -12439,13 +12438,6 @@ "transit_stops": { "description": "Capa que mostra diferents tipus de parades de transport públic.", "filter": { - "0": { - "options": { - "0": { - "question": "Amb refugi" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 36a11b112..348bf81cb 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -2663,17 +2663,6 @@ }, "question": "Má toto místo sanitární skládku?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Toto místo má toalety" - }, - "1": { - "then": "Toto místo nemá toalety" - } - }, - "question": "Má toto místo toalety?" - }, "caravansites-website": { "question": "Má toto místo webové stránky?", "render": "Oficiální webové stránky: {website}" @@ -5831,6 +5820,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "S přístřeškem" + } + } + }, "3": { "options": { "0": { @@ -8623,6 +8619,9 @@ "mappings": { "0": { "then": "Toto parkoviště má 1 místo." + }, + "1": { + "then": "Toto parkoviště má 1 místo." } }, "render": "Toto parkoviště má {capacity} míst." @@ -12500,7 +12499,7 @@ "2": { "then": "Otáčecí kamera" }, - "3": { + "4": { "then": "Domovní zvonek, který lze spouštět kdykoli vzdáleně nebo detekcí pohybu. Jsou to typicky chytré zvonky připojené k Internetu. Typické značky jsou Ring, Google Nest, Eufy…" } }, @@ -13546,13 +13545,6 @@ "transit_stops": { "description": "Vrstva zobrazující různé typy zastávek veřejné dopravy.", "filter": { - "0": { - "options": { - "0": { - "question": "S přístřeškem" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/da.json b/langs/layers/da.json index d1a4d9c39..19ad241fb 100644 --- a/langs/layers/da.json +++ b/langs/layers/da.json @@ -1561,17 +1561,6 @@ }, "question": "Har dette sted en sanitær tømningsstation?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dette sted har toiletter" - }, - "1": { - "then": "Dette sted har ikke toiletter" - } - }, - "question": "Har dette sted toiletter?" - }, "caravansites-website": { "question": "Har dette sted et websted?", "render": "Officiel hjemmeside: {website}" @@ -3152,7 +3141,7 @@ "2": { "then": "Et kamera, der panorerer" }, - "3": { + "4": { "then": "En dørklokke, som kan tændes på afstand når som helst eller ved bevægelsesregistrering. Disse er typisk intelligente internetforbundne dørklokker. Typiske mærker er Ring, Google Nest, Eufy, …" } } diff --git a/langs/layers/de.json b/langs/layers/de.json index 58e89547a..da3f91f1d 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -2439,17 +2439,6 @@ }, "question": "Hat dieser Ort eine sanitäre Entsorgungsstation?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dieser Ort verfügt über Toiletten" - }, - "1": { - "then": "Dieser Ort verfügt nicht über Toiletten" - } - }, - "question": "Verfügt dieser Ort über Toiletten?" - }, "caravansites-website": { "question": "Hat dieser Ort eine Webseite?", "render": "Offizielle Webseite: {website}" @@ -5488,6 +5477,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Mit Unterstand" + } + } + }, "3": { "options": { "0": { @@ -7960,6 +7956,9 @@ "mappings": { "0": { "then": "Dieser Parkplatz hat 1 Stellplatz." + }, + "1": { + "then": "Dieser Parkplatz hat 1 Stellplatz." } }, "render": "Dieser Parkplatz hat {capacity} Stellplätze." @@ -11546,6 +11545,9 @@ "then": "Eine bewegliche Kamera" }, "3": { + "then": "Eine 360°-Kamera" + }, + "4": { "then": "Eine Türklingel, die jederzeit oder per Bewegungserkennung ferngeschaltet werden kann. Dies sind typischerweise Smart, internetgebundene Türklingeln. Typische Marken sind Ring, Google Nest, Eufy, ..." } }, @@ -12428,13 +12430,6 @@ "transit_stops": { "description": "Ebene mit verschiedenen Arten von Haltestellen.", "filter": { - "0": { - "options": { - "0": { - "question": "Mit Unterstand" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/en.json b/langs/layers/en.json index 19193e473..3c1bb83b9 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -531,6 +531,40 @@ "render": "Animal shelter" } }, + "arcade": { + "description": "Layer showing arcades", + "name": "Arcades", + "presets": { + "0": { + "title": "an arcade" + } + }, + "tagRenderings": { + "name": { + "override": { + "question": "What is the name of this arcade?", + "render": "This arcade is called {name}" + } + }, + "virtual_reality": { + "mappings": { + "0": { + "then": "This arcade offers virtual-reality gaming." + }, + "1": { + "then": "This arcade only offers virtual-reality gaming." + }, + "2": { + "then": "This arcade doesn't offer virtual-reality gaming" + } + }, + "question": "Does this arcade offer virtual-reality gaming?" + } + }, + "title": { + "render": "Arcade" + } + }, "artwork": { "description": "An open map of statues, busts, graffitis and other artwork all over the world", "name": "Artworks", @@ -2663,17 +2697,6 @@ }, "question": "Does this place have a sanitary dump station?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "This place has toilets" - }, - "1": { - "then": "This place does not have toilets" - } - }, - "question": "Does this place have toilets?" - }, "caravansites-website": { "question": "Does this place have a website?", "render": "Official website: {website}" @@ -5838,6 +5861,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "With a shelter" + } + } + }, "3": { "options": { "0": { @@ -7016,6 +7046,37 @@ "render": "Hospital" } }, + "hut": { + "description": "Layer showing basic huts, wilderness huts and alpine huts", + "name": "Huts", + "presets": { + "0": { + "description": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas and a fireplace or stove for heating and cooking.", + "title": "wilderness hut" + }, + "1": { + "description": "A serviced remote building located in the mountains intended to provide board and lodging.", + "title": "alpine hut" + }, + "2": { + "description": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas without a fireplace or stove.", + "title": "basic hut" + } + }, + "tagRenderings": { + "drinking_water": { + "mappings": { + "0": { + "then": "Here is drinking water available." + }, + "1": { + "then": "Here is no drinking water available." + } + }, + "question": "Is drinking water available here?" + } + } + }, "hydrant": { "description": "Map layer to show fire hydrants.", "name": "Hydrants", @@ -8626,10 +8687,34 @@ "description": "Layer showing individual parking spaces.", "name": "Parking Spaces", "tagRenderings": { + "access": { + "mappings": { + "0": { + "then": "Anyone can use this parking space." + }, + "1": { + "then": "Anyone can use this parking space." + }, + "2": { + "then": "This parking space is reserved for customers." + }, + "3": { + "then": "This parking space is private and cannot be used by the general public." + }, + "4": { + "then": "This parking space is reserved for permit holders." + } + }, + "question": "Who can use this parking space?", + "render": "Access of parking space: {access}" + }, "capacity": { "mappings": { "0": { "then": "This parking space has 1 space." + }, + "1": { + "then": "This parking space has 1 space." } }, "render": "This parking spaces has {capacity} spaces." @@ -8654,6 +8739,9 @@ "13": { "then": "This is a parking space reserved for car sharing." }, + "14": { + "then": "This is a parking space reserved for women." + }, "2": { "then": "This is a disabled parking space." }, @@ -8785,6 +8873,130 @@ "render": "Physiotherapist {name}" } }, + "picnic_site": { + "description": "Picnic sites for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "filter": { + "1": { + "options": { + "0": { + "question": "With a firepit" + } + } + }, + "2": { + "options": { + "0": { + "question": "With a BBQ" + } + } + }, + "3": { + "options": { + "0": { + "question": "With drinking water" + } + } + } + }, + "name": "Picnic sites", + "presets": { + "0": { + "description": "A picnic site for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "title": "a picnic site" + } + }, + "tagRenderings": { + "bbq": { + "mappings": { + "0": { + "then": "This picnic site has a BBQ." + }, + "1": { + "then": "This picnic site does not have a BBQ." + }, + "2": { + "then": "This picnic site has a BBQ, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have a BBQ?" + }, + "covered": { + "mappings": { + "0": { + "then": "This picnic site is covered." + }, + "1": { + "then": "This picnic site is not covered." + } + }, + "question": "Is this picnic site covered?" + }, + "drinking_water": { + "mappings": { + "0": { + "then": "This picnic site has drinking water." + }, + "1": { + "then": "This picnic site does not have drinking water." + }, + "2": { + "then": "This picnic site has drinking water, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have drinking water?" + }, + "fireplace": { + "mappings": { + "0": { + "then": "This picnic site has a firepit." + }, + "1": { + "then": "This picnic site does not have a firepit." + }, + "2": { + "then": "This picnic site has a firepit, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have a firepit?" + }, + "name": { + "override": { + "render": "This picnic site is called {name}" + } + }, + "openfire": { + "mappings": { + "0": { + "then": "Open fire is allowed at this picnic site." + }, + "1": { + "then": "Open fire is not allowed at this picnic site." + }, + "2": { + "then": "Open fire is allowed at this picnic site with a permit." + } + }, + "question": "Is open fire allowed at this picnic site?" + }, + "shelter": { + "mappings": { + "0": { + "then": "This picnic site has a shelter." + }, + "1": { + "then": "This picnic site does not have a shelter." + }, + "2": { + "then": "This picnic site has a shelter, but is is mapped as a different icon." + } + }, + "question": "Does this picnic site have a shelter?" + } + }, + "title": { + "render": "Picnic site" + } + }, "picnic_table": { "description": "The layer showing picnic tables", "name": "Picnic tables", @@ -12543,6 +12755,9 @@ "then": "A panning camera" }, "3": { + "then": "A 360° camera" + }, + "4": { "then": "A doorbell which might be turned on remotely at any time or by motion detection. These are typically Smart, internet-connected doorbells. Typical brands are Ring, Google Nest, Eufy, …" } }, @@ -13588,13 +13803,6 @@ "transit_stops": { "description": "Layer showing different types of transit stops.", "filter": { - "0": { - "options": { - "0": { - "question": "With a shelter" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/es.json b/langs/layers/es.json index 0baced4d3..076643f52 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -2270,17 +2270,6 @@ }, "question": "¿Este lugar tiene un punto de vaciado de aguas grises?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tiene baños" - }, - "1": { - "then": "Este lugar no tiene baños" - } - }, - "question": "¿Este lugar tiene baños?" - }, "caravansites-website": { "question": "¿Este lugar tiene una página web?", "render": "Página web oficial: {website}" @@ -5162,6 +5151,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Con refugio" + } + } + }, "3": { "options": { "0": { @@ -7594,6 +7590,9 @@ "mappings": { "0": { "then": "Esta plaza de aparcamiento tiene 1 plaza." + }, + "1": { + "then": "Esta plaza de aparcamiento tiene 1 plaza." } }, "render": "Esta plaza de aparcamiento tiene {capacity} plazas." @@ -10582,6 +10581,9 @@ }, "2": { "then": "Una cámara panorámica" + }, + "3": { + "then": "Una cámara de 360°" } }, "question": "¿Qué tipo de cámara es esta?" @@ -11317,13 +11319,6 @@ "transit_stops": { "description": "Capa que muestra diferentes tipos de paradas de transporte.", "filter": { - "0": { - "options": { - "0": { - "question": "Con refugio" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/eu.json b/langs/layers/eu.json index 30ad60b23..8473c20b9 100644 --- a/langs/layers/eu.json +++ b/langs/layers/eu.json @@ -289,13 +289,6 @@ } } }, - "caravansites-toilets": { - "mappings": { - "1": { - "then": "Toki honek ez dauka komunik" - } - } - }, "caravansites-website": { "question": "Toki honek webgunerik ba al du?" } diff --git a/langs/layers/fr.json b/langs/layers/fr.json index dad2406a9..cceb1374f 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -1873,17 +1873,6 @@ }, "question": "Ce site possède-t’il un lieu de vidange ?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Ce site a des toilettes" - }, - "1": { - "then": "Ce site n’a pas de toilettes" - } - }, - "question": "Y-a-t’il des toilettes sur le site ?" - }, "caravansites-website": { "question": "Ce lieu a-t’il un site internet ?", "render": "Site officiel : {website}" @@ -3609,6 +3598,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Avec un abri" + } + } + }, "6": { "options": { "0": { @@ -6519,6 +6515,9 @@ }, "2": { "then": "Une caméra panoramique" + }, + "3": { + "then": "Une caméra 360°" } }, "question": "Quel genre de caméra est-ce ?" @@ -6543,7 +6542,7 @@ "then": "Une zone intérieure privée est surveillée, par exemple un magasin, un parking souterrain privé…" } }, - "question": "De quel genre de surveillance cette caméra est-elle ?" + "question": "De quel genre de surveillance cette caméra est-elle ?" }, "Surveillance:zone": { "mappings": { @@ -6916,13 +6915,6 @@ }, "transit_stops": { "filter": { - "0": { - "options": { - "0": { - "question": "Avec un abri" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/hu.json b/langs/layers/hu.json index d1e1287f3..c95ddb68a 100644 --- a/langs/layers/hu.json +++ b/langs/layers/hu.json @@ -466,19 +466,6 @@ "description": "Új hivatalos lakóautóhely hozzáadása. Ez arra vannak kijelölve, hogy lakóautóval ott éjszakázzunk. Lehet, hogy úgy néz ki, mint egy igazi kemping, de az is lehet, hogy csak olyan, mint egy parkoló. Előfordulhat, hogy egyáltalán nem jelzik őket, hanem csak egy önkormányzati határozatban vannak kijelölve. A lakóautósoknak szánt olyan hagyományos parkolók, ahol nem várhatóan nem fognak éjszakázni, -nem minősül- lakóautóhelynek. ", "title": "lakóautós megállóhely" } - }, - "tagRenderings": { - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Itt van WC" - }, - "1": { - "then": "Itt nincs WC" - } - }, - "question": "Van-e itt WC?" - } } }, "charging_station": { diff --git a/langs/layers/id.json b/langs/layers/id.json index cc5cd707c..beb701796 100644 --- a/langs/layers/id.json +++ b/langs/layers/id.json @@ -196,16 +196,6 @@ }, "question": "Apakah tempat ini memiliki tempat pembuangan sanitasi?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Tempat sini ada tandas" - }, - "1": { - "then": "Tempat sini tiada tandas" - } - } - }, "caravansites-website": { "question": "Tempat sini terada situs web?", "render": "Situs resmi: {website}" diff --git a/langs/layers/it.json b/langs/layers/it.json index 65a7fd8d5..990c15157 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -2632,17 +2632,6 @@ }, "question": "Questo posto ha una stazione di scarico sanitario?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Questo posto ha servizi igienici" - }, - "1": { - "then": "Questo posto non ha servizi igienici" - } - }, - "question": "Questo posto ha servizi igienici?" - }, "caravansites-website": { "question": "Questo posto ha un sito web?", "render": "Sito web ufficiale: {website}" @@ -5758,6 +5747,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Con una pensilina" + } + } + }, "3": { "options": { "0": { @@ -8459,6 +8455,9 @@ "mappings": { "0": { "then": "Questo posto auto ha 1 spazio." + }, + "1": { + "then": "Questo posto auto ha 1 spazio." } }, "render": "Questo posto auto ha {capacity} spazi." @@ -12109,7 +12108,7 @@ "2": { "then": "Una telecamera panoramica" }, - "3": { + "4": { "then": "Un campanello che potrebbe essere acceso da remoto in qualsiasi momento o tramite rilevamento del movimento. Questi sono tipicamente campanelli Smart, connessi a Internet. Marchi tipici sono Ring, Google Nest, Eufy, ..." } }, @@ -13124,13 +13123,6 @@ "transit_stops": { "description": "Livello che mostra diversi tipi di fermate dei mezzi pubblici.", "filter": { - "0": { - "options": { - "0": { - "question": "Con una pensilina" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/ja.json b/langs/layers/ja.json index b9808a0b2..7ee7a672c 100644 --- a/langs/layers/ja.json +++ b/langs/layers/ja.json @@ -217,17 +217,6 @@ }, "question": "この場所に衛生的なゴミ捨て場はありますか?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "ここにはトイレがある" - }, - "1": { - "then": "ここにはトイレがない" - } - }, - "question": "ここにトイレはありますか?" - }, "caravansites-website": { "question": "ここにはウェブサイトがありますか?", "render": "公式Webサイト: {website}" diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json index 2879f7599..e85806b21 100644 --- a/langs/layers/nb_NO.json +++ b/langs/layers/nb_NO.json @@ -443,17 +443,6 @@ "question": "Hva heter dette stedet?", "render": "Dette stedet heter {name}" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dette stedet har toalettfasiliteter" - }, - "1": { - "then": "Dette stedet har ikke toalettfasiliteter" - } - }, - "question": "Har dette stedet toaletter?" - }, "caravansites-website": { "question": "Har dette stedet en nettside?", "render": "Offisiell nettside: {website}" diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 73aaffd8e..865edb6e9 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2546,17 +2546,6 @@ }, "question": "Heeft deze plaats een loosplaats?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Deze plaats heeft toiletten" - }, - "1": { - "then": "Deze plaats heeft geen toiletten" - } - }, - "question": "Heeft deze plaats toiletten?" - }, "caravansites-website": { "question": "Heeft deze plaats een website?", "render": "Officiële website: : {website}" @@ -7622,10 +7611,34 @@ "description": "Laag met individuele parkeerplekken.", "name": "Parkeerplekken", "tagRenderings": { + "access": { + "mappings": { + "0": { + "then": "Iedereen kan deze parkeerplek gebruiken." + }, + "1": { + "then": "Iedereen kan deze parkeerplek gebruiken." + }, + "2": { + "then": "Deze parkeerplek is gereserveerd voor klanten." + }, + "3": { + "then": "Deze parkeerplek is privé en mag niet door het grote publiek worden gebruikt." + }, + "4": { + "then": "Deze parkeerplek is gereserveerd voor vergunninghouders." + } + }, + "question": "Wie mag deze parkeerplek gebruiken?", + "render": "Toegang tot parkeerplek: {access}" + }, "capacity": { "mappings": { "0": { "then": "Deze parkeerplek heeft 1 plaats." + }, + "1": { + "then": "Deze parkeerplek heeft 1 plaats." } }, "render": "Deze parkeerplek heeft {capacity} plaatsen." @@ -7650,6 +7663,9 @@ "13": { "then": "Deze parkeerplek is gereserveerd voor autodelen." }, + "14": { + "then": "Deze parkeerplek is gereserveerd voor vrouwen." + }, "2": { "then": "Dit is een gehandicaptenparkeerplaats." }, @@ -7780,6 +7796,130 @@ "render": "Kinesist {name}" } }, + "picnic_site": { + "description": "Picknickplaatsen voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen", + "filter": { + "1": { + "options": { + "0": { + "question": "Met een vuurplaats" + } + } + }, + "2": { + "options": { + "0": { + "question": "Met een BBQ" + } + } + }, + "3": { + "options": { + "0": { + "question": "Met drinkwater" + } + } + } + }, + "name": "Picknickplaatsen", + "presets": { + "0": { + "description": "Een picknickplaats voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen", + "title": "een picknickplaats" + } + }, + "tagRenderings": { + "bbq": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een BBQ." + }, + "1": { + "then": "Deze picknickplaats heeft geen BBQ." + }, + "2": { + "then": "Deze picknickplaats heeft een BBQ, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een BBQ?" + }, + "covered": { + "mappings": { + "0": { + "then": "Deze picknickplaats is overdekt." + }, + "1": { + "then": "Deze picknickplaats is niet overdekt." + } + }, + "question": "Is deze picknickplaats overdekt?" + }, + "drinking_water": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft drinkwater." + }, + "1": { + "then": "Deze picknickplaats heeft geen drinkwater." + }, + "2": { + "then": "Deze picknickplaats heeft drinkwater, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats drinkwater?" + }, + "fireplace": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een vuurplaats." + }, + "1": { + "then": "Deze picknickplaats heeft geen vuurplaats." + }, + "2": { + "then": "Deze picknickplaats heeft een vuurplaats, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een vuurplaats?" + }, + "name": { + "override": { + "render": "Deze picknickplaats heet {name}" + } + }, + "openfire": { + "mappings": { + "0": { + "then": "Open vuur is toegestaan op deze picknickplaats." + }, + "1": { + "then": "Open vuur is niet toegestaan op deze picknickplaats." + }, + "2": { + "then": "Open vuur is toegestaan op deze picknickplaats met een vergunning." + } + }, + "question": "Is open vuur toegestaan op deze picknickplaats?" + }, + "shelter": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een schuilplaats." + }, + "1": { + "then": "Deze picknickplaats heeft geen schuilplaats." + }, + "2": { + "then": "Deze picknickplaats heeft een schuilplaats, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een schuilplaats?" + } + }, + "title": { + "render": "Picknickplaats" + } + }, "picnic_table": { "description": "Deze laag toont picknicktafels", "name": "Picknicktafels", diff --git a/langs/layers/pl.json b/langs/layers/pl.json index decd96ce4..187e36d08 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -1143,17 +1143,6 @@ }, "question": "Czy w tym miejscu znajduje się stacja zrzutu ścieków sanitarnych?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "To miejsce ma toalety" - }, - "1": { - "then": "To miejsce nie ma toalet" - } - }, - "question": "Czy to miejsce ma toalety?" - }, "caravansites-website": { "question": "Czy to miejsce ma stronę internetową?", "render": "Official website: {website}" diff --git a/langs/layers/pt.json b/langs/layers/pt.json index 6e2b577c2..b1599bf54 100644 --- a/langs/layers/pt.json +++ b/langs/layers/pt.json @@ -1418,17 +1418,6 @@ }, "question": "Este local tem uma estação de aterro sanitário?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tem casa de banho" - }, - "1": { - "then": "Este lugar não tem casas de banho" - } - }, - "question": "Este lugar tem casas de banho?" - }, "caravansites-website": { "question": "Este lugar tem um website?", "render": "Site oficial: {website}" diff --git a/langs/layers/pt_BR.json b/langs/layers/pt_BR.json index 2834d9a0c..6da173f4d 100644 --- a/langs/layers/pt_BR.json +++ b/langs/layers/pt_BR.json @@ -1428,17 +1428,6 @@ }, "question": "Este local tem uma estação de aterro sanitário?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tem banheiros" - }, - "1": { - "then": "Este lugar não tem banheiros" - } - }, - "question": "Este lugar tem banheiros?" - }, "caravansites-website": { "question": "Este lugar tem um website?", "render": "Site oficial: {website}" diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 4cda3d5e1..915e17453 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -710,17 +710,6 @@ }, "question": "В этом кемпинге есть место для слива отходов из туалетных резервуаров?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "В этом месте есть туалеты" - }, - "1": { - "then": "В этом месте нет туалетов" - } - }, - "question": "Здесь есть туалеты?" - }, "caravansites-website": { "question": "Есть ли у этого места веб-сайт?", "render": "Официальный сайт: {website}" diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json index 44fd18acb..adf784206 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -594,17 +594,6 @@ }, "question": "這個地方有衛生設施嗎?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "這個地方有廁所" - }, - "1": { - "then": "這個地方並沒有廁所" - } - }, - "question": "這個地方有廁所嗎?" - }, "caravansites-website": { "question": "這個地方有網站嗎?", "render": "官方網站:{website}" diff --git a/langs/themes/en.json b/langs/themes/en.json index de5ff9837..0c7b8469d 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -8,6 +8,10 @@ "description": "On this map, one can find and mark nearby defibrillators", "title": "Defibrillators" }, + "arcade": { + "description": "A map of arcades", + "title": "Arcades" + }, "architecture": { "description": "A map showing the architectural style of buildings", "title": "Buildings with an architectural style" diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 34744bb80..63aa24107 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -5444,6 +5444,47 @@ button.unstyled, .button-unstyled button { padding: 0; } +/****** Tablist elements *****/ + +.tablist { + margin: 0.25rem; + padding: 0.5rem; + border: 2px dashed var(--button-background-hover); + border-radius: 0.5rem; + display: flex; + justify-content: center; + flex-wrap: wrap; +} + +.tab { + border: unset; + border-radius: 0; + transition: all; + color: var(--foreground-color); + border-bottom: 2px solid var(--foreground-color); + font-weight: bold; + margin: 0.25rem; + padding: 0.25rem; + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.tab-selected { + opacity: 100%; + background: var(--interactive-background); +} + +/* Actually used, don't remove*/ + +.tab-unselected { + background: #00000000 !important; + opacity: 60%; +} + +.tab-unselected:hover { + background: var(--interactive-background); +} + /******* Other input elements ******/ .hover-alert:hover { diff --git a/src/Logic/Actors/SelectedElementTagsUpdater.ts b/src/Logic/Actors/SelectedElementTagsUpdater.ts index 67a7c2b44..f34ec863e 100644 --- a/src/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/src/Logic/Actors/SelectedElementTagsUpdater.ts @@ -10,6 +10,7 @@ import { Changes } from "../Osm/Changes" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState" +import Objects from "../../Utils/Objects" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -160,7 +161,7 @@ export default class SelectedElementTagsUpdater { const newGeometry = osmObject.asGeoJson()?.geometry const oldFeature = state.indexedFeatures.featuresById.data.get(id) const oldGeometry = oldFeature?.geometry - if (oldGeometry !== undefined && !Utils.SameObject(newGeometry, oldGeometry)) { + if (oldGeometry !== undefined && !Objects.sameObject(newGeometry, oldGeometry)) { console.log("Detected a difference in geometry for ", id) this.invalidateCache(s) oldFeature.geometry = newGeometry diff --git a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index c9cb4b04c..8f3d009f2 100644 --- a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -4,9 +4,9 @@ import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" import FeaturePropertiesStore from "./FeaturePropertiesStore" import { UIEventSource } from "../../UIEventSource" -import { Utils } from "../../../Utils" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" +import { Lists } from "../../../Utils/Lists" class SingleTileSaver { private readonly _storage: UIEventSource @@ -31,7 +31,7 @@ class SingleTileSaver { } public saveFeatures(features: Feature[]) { - if (Utils.sameList(features, this._storage.data)) { + if (Lists.sameList(features, this._storage.data)) { return } for (const feature of features) { diff --git a/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts b/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index dee5f8994..90cc9ef1b 100644 --- a/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts +++ b/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -6,7 +6,7 @@ import { Stores, UIEventSource } from "../../UIEventSource" import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" import { Feature } from "geojson" -import { Utils } from "../../../Utils" +import Objects from "../../../Utils/Objects" export default class ChangeGeometryApplicator implements FeatureSource { public readonly features: UIEventSource = new UIEventSource([]) @@ -69,7 +69,7 @@ export default class ChangeGeometryApplicator implements FeatureSource { // We only apply the last change as that one'll have the latest geometry const change = changesForFeature[changesForFeature.length - 1] copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) - if (Utils.SameObject(copy.geometry, feature.geometry)) { + if (Objects.sameObject(copy.geometry, feature.geometry)) { // No actual changes: pass along the original newFeatures.push(feature) continue diff --git a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts index 37be66206..7cbad0368 100644 --- a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -9,7 +9,7 @@ import { BBox } from "../../BBox" import { OsmFeature } from "../../../Models/OsmFeature" import { Lists } from "../../../Utils/Lists" -;("use strict") +("use strict") /** * A wrapper around the 'Overpass'-object. @@ -229,7 +229,7 @@ export default class OverpassFeatureSource im const requestedBounds = this.state.bounds.data if ( this._lastQueryBBox !== undefined && - Utils.sameList(this._layersToDownload.data, this._lastRequestedLayers) && + Lists.sameList(this._layersToDownload.data, this._lastRequestedLayers) && requestedBounds.isContainedIn(this._lastQueryBBox) ) { return undefined diff --git a/src/Logic/Search/FilterSearch.ts b/src/Logic/Search/FilterSearch.ts index 6dbf346f2..7cc80b653 100644 --- a/src/Logic/Search/FilterSearch.ts +++ b/src/Logic/Search/FilterSearch.ts @@ -32,7 +32,7 @@ export default class FilterSearch { .split(" ") .map((query) => { if (!Strings.isEmoji(query)) { - return Utils.simplifyStringForSearch(query) + return Strings.simplifyStringForSearch(query) } return query }) @@ -64,7 +64,7 @@ export default class FilterSearch { option.searchTerms?.["en"] ?? []), ].flatMap((term) => [term, ...(term?.split(" ") ?? [])]) - terms = terms.map((t) => Utils.simplifyStringForSearch(t)) + terms = terms.map((t) => Strings.simplifyStringForSearch(t)) terms.push(option.emoji) Lists.noNullInplace(terms) const distances = queries.flatMap((query) => diff --git a/src/Logic/Search/LayerSearch.ts b/src/Logic/Search/LayerSearch.ts index 3d23767c4..ebe644122 100644 --- a/src/Logic/Search/LayerSearch.ts +++ b/src/Logic/Search/LayerSearch.ts @@ -2,7 +2,7 @@ import SearchUtils from "./SearchUtils" import ThemeSearch from "./ThemeSearch" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" -import { Utils } from "../../Utils" +import { Strings } from "../../Utils/Strings" export default class LayerSearch { private readonly _theme: ThemeConfig @@ -24,7 +24,7 @@ export default class LayerSearch { const queryParts = query .trim() .split(" ") - .map((q) => Utils.simplifyStringForSearch(q)) + .map((q) => Strings.simplifyStringForSearch(q)) for (const id in ThemeSearch.officialThemes.layers) { if (options?.whitelist && !options?.whitelist.has(id)) { continue diff --git a/src/Logic/Search/LocalElementSearch.ts b/src/Logic/Search/LocalElementSearch.ts index b34e34bf4..e4d697816 100644 --- a/src/Logic/Search/LocalElementSearch.ts +++ b/src/Logic/Search/LocalElementSearch.ts @@ -6,6 +6,7 @@ import { GeoOperations } from "../GeoOperations" import { ImmutableStore, Store, Stores } from "../UIEventSource" import OpenStreetMapIdSearch from "./OpenStreetMapIdSearch" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" type IntermediateResult = { feature: Feature @@ -61,7 +62,7 @@ export default class LocalElementSearch implements GeocodingProvider { ...searchTerms .flatMap((entry) => entry.split(/ /)) .map((entry) => { - let simplified = Utils.simplifyStringForSearch(entry) + let simplified = Strings.simplifyStringForSearch(entry) if (matchStart) { simplified = simplified.slice(0, query.length) } @@ -103,7 +104,7 @@ export default class LocalElementSearch implements GeocodingProvider { const centerPoint: [number, number] = [center.lon, center.lat] const properties = this._state.perLayer const candidateId = OpenStreetMapIdSearch.extractId(query) - query = Utils.simplifyStringForSearch(query) + query = Strings.simplifyStringForSearch(query) const partials: Store[] = [] diff --git a/src/Logic/Search/SearchUtils.ts b/src/Logic/Search/SearchUtils.ts index a50ac5bf7..e5f993de2 100644 --- a/src/Logic/Search/SearchUtils.ts +++ b/src/Logic/Search/SearchUtils.ts @@ -2,6 +2,7 @@ import Locale from "../../UI/i18n/Locale" import { Utils } from "../../Utils" import ThemeSearch from "./ThemeSearch" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" export default class SearchUtils { /** Applies special search terms, such as 'studio', 'osmcha', ... @@ -60,7 +61,7 @@ export default class SearchUtils { const queryParts = query .trim() .split(" ") - .map((q) => Utils.simplifyStringForSearch(q)) + .map((q) => Strings.simplifyStringForSearch(q)) let terms: string[] if (Array.isArray(keywords)) { terms = keywords @@ -74,7 +75,7 @@ export default class SearchUtils { const q = queryParts[i] let minDistance: number = 99 for (const term of termsAll) { - const d = Utils.levenshteinDistance(q, Utils.simplifyStringForSearch(term)) + const d = Utils.levenshteinDistance(q, Strings.simplifyStringForSearch(term)) if (d < minDistance) { minDistance = d } diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index b1b69b8ab..77ec8e5bb 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -314,6 +314,14 @@ export class And extends TagsFilter { for (let j = i + 1; j < optimized.length; j++) { const ti = optimized[i] const tj = optimized[j] + if ( + !ti || + !tj || + typeof ti.shadows !== "function" || + typeof tj.shadows !== "function" + ) { + continue + } if (ti.shadows(tj)) { // if 'ti' is true, this implies 'tj' is always true as well. // if 'ti' is false, then 'tj' might be true or false @@ -322,6 +330,7 @@ export class And extends TagsFilter { // If 'ti' is true, then 'tj' will be true too and 'tj' can be ignored // If 'ti' is false, then the entire expression will be false and it doesn't matter what 'tj' yields optimized.splice(j, 1) + j-- } else if (tj.shadows(ti)) { optimized.splice(i, 1) i-- diff --git a/src/Logic/Tags/SubstitutingTag.ts b/src/Logic/Tags/SubstitutingTag.ts index e44e28394..9930a1de1 100644 --- a/src/Logic/Tags/SubstitutingTag.ts +++ b/src/Logic/Tags/SubstitutingTag.ts @@ -120,7 +120,7 @@ export default class SubstitutingTag extends TagsFilter { } isNegative(): boolean { - return false + return this._value === "" } visit(f: (tagsFilter: TagsFilter) => void) { diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index c09a77617..e4aba8209 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -994,11 +994,31 @@ export class TagUtils { ].join("\n") } - static fromProperties(tags: Record): TagConfigJson | boolean { + public static fromProperties(tags: Record): TagConfigJson | boolean { const opt = new And(Object.keys(tags).map((k) => new Tag(k, tags[k]))).optimize() if (opt === true || opt === false) { return opt } return opt.asJson() } + + /** + * Returns a similarly structured tag, but all tags with an empty value are removed. + * Those are assumed to be all met (and thus true) + * + * new And([new Tag("a", "b"), new Tag("c", "")] // => new Tag("a","b") + * new And([new Tag("c", "")] // => true + */ + public static removeEmptyParts(tag: UploadableTag): UploadableTag | true { + if (tag["and"]) { + const tags = tag["and"] + const cleaned = tags.map(t => TagUtils.removeEmptyParts(t)) + const filtered = cleaned.filter(t => t !== true) + return new And(filtered) + } + if (tag.isNegative()) { + return true + } + return tag + } } diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 015715952..8d31037ec 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -1,5 +1,6 @@ import { Utils } from "../Utils" import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store" +import { Lists } from "../Utils/Lists" /** * Various static utils @@ -66,7 +67,7 @@ export class Stores { stable.setData(undefined) return } - if (Utils.sameList(stable.data, list)) { + if (Lists.sameList(stable.data, list)) { return } stable.setData(list) diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 4d4919442..c4383e497 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -25,6 +25,7 @@ import { eliCategory } from "../../RasterLayerProperties" import licenses from "../../../assets/generated/license_info.json" import { Strings } from "../../../Utils/Strings" import { Lists } from "../../../Utils/Lists" +import Objects from "../../../Utils/Objects" export class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] @@ -1085,11 +1086,8 @@ export class DetectDuplicatePresets extends DesugaringStep { const presetBTags = optimizedTags[j] const presetB = presets[j] if ( - Utils.SameObject(presetATags, presetBTags) && - Utils.sameList( - presetA.preciseInput.snapToLayers, - presetB.preciseInput.snapToLayers - ) + Objects.sameObject(presetATags, presetBTags) && + Lists.sameList(presetA.preciseInput.snapToLayers, presetB.preciseInput.snapToLayers) ) { context.err( `This theme has multiple presets with the same tags: ${presetATags.asHumanString( diff --git a/src/UI/Base/TabbedGroup.svelte b/src/UI/Base/TabbedGroup.svelte index 586f7daf8..8c692740a 100644 --- a/src/UI/Base/TabbedGroup.svelte +++ b/src/UI/Base/TabbedGroup.svelte @@ -43,11 +43,11 @@ } }} > -
- +
+ {#if $$slots.title0} twJoin("tab", selected && "primary", !$condition0 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition0 && "hidden")} >
Tab 0 @@ -56,7 +56,7 @@ {/if} {#if $$slots.title1} twJoin("tab", selected && "primary", !$condition1 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition1 && "hidden")} >
@@ -65,7 +65,7 @@ {/if} {#if $$slots.title2} twJoin("tab", selected && "primary", !$condition2 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition2 && "hidden")} >
@@ -74,7 +74,7 @@ {/if} {#if $$slots.title3} twJoin("tab", selected && "primary", !$condition3 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition3 && "hidden")} >
@@ -83,7 +83,7 @@ {/if} {#if $$slots.title4} twJoin("tab", selected && "primary", !$condition4 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition4 && "hidden")} >
@@ -92,7 +92,7 @@ {/if} {#if $$slots.title5} twJoin("tab", selected && "primary", !$condition5 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition5 && "hidden")} >
@@ -101,7 +101,7 @@ {/if} {#if $$slots.title6} twJoin("tab", selected && "primary", !$condition6 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition6 && "hidden")} >
@@ -167,19 +167,6 @@ height: calc(100% - 2rem); } - :global(.tab) { - margin: 0.25rem; - padding: 0.25rem; - padding-left: 0.75rem; - padding-right: 0.75rem; - border-radius: 1rem; - } - - :global(.tab .flex) { - align-items: center; - gap: 0.25rem; - } - :global(.tab span|div) { align-items: center; gap: 0.25rem; @@ -190,8 +177,4 @@ fill: var(--interactive-contrast); } - :global(.tab-unselected) { - background-color: var(--background-color) !important; - color: var(--foreground-color) !important; - } diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index aaaa2e239..b6a0465b0 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -55,15 +55,13 @@ import ImageUploadQueue from "../../Logic/ImageProviders/ImageUploadQueue" import QueuedImagesView from "../Image/QueuedImagesView.svelte" import InsetSpacer from "../Base/InsetSpacer.svelte" - import UserCircle from "@rgossiaux/svelte-heroicons/solid/UserCircle" import OfflineManagement from "./OfflineManagement.svelte" import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica" import { onDestroy } from "svelte" import Avatar from "../Base/Avatar.svelte" - import { SpecialVisualizationSvelte } from "../SpecialVisualization" - import ThemeViewState from "../../Models/ThemeViewState" import { Changes } from "../../Logic/Osm/Changes" import PendingChangesView from "./PendingChangesView.svelte" + import { DevicePhoneMobileIcon } from "@babeard/svelte-heroicons/solid" export let state: { favourites: FavouritesFeatureSource @@ -236,6 +234,12 @@ + {#if !$isAndroid} + + + + + {/if} diff --git a/src/UI/History/AggregateView.svelte b/src/UI/History/AggregateView.svelte index 35ce509cd..130e7c2dd 100644 --- a/src/UI/History/AggregateView.svelte +++ b/src/UI/History/AggregateView.svelte @@ -12,9 +12,9 @@ import Translations from "../i18n/Translations" import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" import { Or } from "../../Logic/Tags/Or" - import { Utils } from "../../Utils" import ChartJs from "../Base/ChartJs.svelte" import { ChartJsUtils } from "../Base/ChartJsUtils" + import { Lists } from "../../Utils/Lists" export let onlyShowUsername: string[] export let features: Feature[] diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte index 5afae05be..06c93d9bc 100644 --- a/src/UI/InspectorGUI.svelte +++ b/src/UI/InspectorGUI.svelte @@ -33,6 +33,9 @@ import GeocodeResults from "./Search/GeocodeResults.svelte" import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle" import type { GeocodeResult } from "../Logic/Search/GeocodingProvider" + import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui" + import WikipediaTitle from "./Wikipedia/WikipediaTitle.svelte" + import WikipediaArticle from "./Wikipedia/WikipediaArticle.svelte" console.log("Loading inspector GUI") let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") @@ -49,7 +52,7 @@ new CoordinateSearch(), new OpenLocationCodeSearch(), new PhotonSearch(true, 2), - new PhotonSearch() + new PhotonSearch(), ) let showSearchDrawer = new UIEventSource(true) let searchIsFocussed = new UIEventSource(false) @@ -138,7 +141,7 @@ const overpass = new Overpass( Constants.defaultOverpassUrls[0], undefined, - user.split(";").map((user) => 'nw(user_touched:"' + user + '");') + user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"), ) if (!maplibremap.bounds.data) { return @@ -161,8 +164,6 @@ return true }) - let mode: "map" | "table" | "aggregate" | "images" = "map" - let showPreviouslyVisited = new UIEventSource(true) const t = Translations.t.inspector @@ -181,8 +182,8 @@
-

+

-
- - - - -
+ + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> - {#if mode === "map"} - {#if $selectedElement !== undefined} - - + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + + + + + + {#if $selectedElement !== undefined} + +
- - {$selectedElement.properties.id} - - selectedElement.set(undefined)} /> -
+ > + +
+ + {$selectedElement.properties.id} + + selectedElement.set(undefined)} /> +
- -
- - {/if} - -
- -
- search()} - isFocused={searchIsFocussed} - value={searchvalue} - on:focus={() => state.searchState.showSearchDrawer.set(true)} - /> - {#if $searchSuggestions?.length > 0 || $searchIsFocussed} - search(event.detail)} /> + + + {/if} -
-
- {:else if mode === "table"} -
- {#each $featuresStore as f} - - {/each} -
- {:else if mode === "aggregate"} -
- -
- {:else if mode === "images"} -
- -
- {/if} + +
+ +
+ search()} + isFocused={searchIsFocussed} + value={searchvalue} + on:focus={() => state.searchState.showSearchDrawer.set(true)} + /> + {#if $searchSuggestions?.length > 0 || $searchIsFocussed} + search(event.detail)} /> + {/if} +
+
+ + + + + {#each $featuresStore as f} + + {/each} + + + + + + + + + +
diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index dcbe8c80a..1cc80257e 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -30,6 +30,8 @@ import Tr from "../Base/Tr.svelte" import Combine from "../Base/Combine" import Marker from "../Map/Marker.svelte" import { twJoin } from "tailwind-merge" +import { Tag } from "../../Logic/Tags/Tag" +import { Lists } from "../../Utils/Lists" class DirectionIndicatorVis extends SpecialVisualizationSvelte { funcName = "direction_indicator" @@ -243,17 +245,24 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { console.warn("Trying to use the _original_ layer") layer = state.theme.layers.find((l) => l.id === layer._basedOn) ?? layer } + + const allKeys = Lists.dedup(layer.presets.flatMap(preset => preset.tags.flatMap(tag => tag.usedKeys()))) + const question: QuestionableTagRenderingConfigJson = { id: layer.id + "-type", question: t.question.translations, - mappings: layer.presets.map((pr) => ({ - if: new And(pr.tags).asJson(), - icon: "auto", - then: (pr.description ? t.typeDescription : t.typeTitle).Subs({ - title: pr.title, - description: pr.description, - }).translations, - })), + mappings: layer.presets.map((pr) => { + const presetKeys = new Set(pr.tags.flatMap(t => t.key)) + const keysToRemove = allKeys.filter(k => !presetKeys.has(k)).map(k => new Tag(k, "")) + return ({ + if: new And([...pr.tags, ...keysToRemove]).asJson(), + icon: "auto", + then: (pr.description ? t.typeDescription : t.typeTitle).Subs({ + title: pr.title, + description: pr.description, + }).translations, + }) + }), } if (question.mappings.length === 0) { console.error("No mappings for preset_type_select, something went wrong") diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index c4846b1b2..a2dcaab6e 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -12,6 +12,7 @@ import { Utils } from "../../../Utils" import { twMerge } from "tailwind-merge" import EditButton from "./EditButton.svelte" + import { Strings } from "../../../Utils/Strings" export let config: TagRenderingConfig export let tags: UIEventSource> @@ -83,7 +84,7 @@ onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting())) onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting())) } - let answerId = "answer-" + Utils.randomString(5) + let answerId = "answer-" + Strings.randomString(5) let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false) let apiState: Store = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online") diff --git a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte index 44b99286f..f7e365f1c 100644 --- a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte @@ -44,8 +44,9 @@ } function getAutoIcon(mapping: { readonly if?: TagsFilter }): Readonly> { + const ifTags = TagUtils.removeEmptyParts(mapping.if) for (const preset of layer.presets) { - if (!new And(preset.tags).shadows(mapping.if)) { + if (!new And(preset.tags).shadows(ifTags)) { continue } diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index f73137ab5..594776421 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -1,12 +1,7 @@ import { ConfigMeta } from "./configMeta" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" -import { - Conversion, - ConversionMessage, - DesugaringContext, - Pipe, -} from "../../Models/ThemeConfig/Conversion/Conversion" +import { Conversion, ConversionMessage, DesugaringContext, Pipe } from "../../Models/ThemeConfig/Conversion/Conversion" import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation" import { AllSharedLayers } from "../../Customizations/AllSharedLayers" @@ -26,6 +21,7 @@ import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderi import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme" import * as questions from "../../../public/assets/generated/layers/questions.json" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" export interface HighlightedTagRendering { path: ReadonlyArray @@ -431,7 +427,7 @@ export default class EditLayerState extends EditJsonState { } if (!tr["id"] && !tr["override"]) { const qtr = tr - let id = "" + i + "_" + Utils.randomString(5) + let id = "" + i + "_" + Strings.randomString(5) if (qtr?.freeform?.key) { id = qtr?.freeform?.key } else if (qtr.mappings?.[0]?.if) { diff --git a/src/UI/Wikipedia/WikipediaPanel.svelte b/src/UI/Wikipedia/WikipediaPanel.svelte index ac9e710d5..43db3d34a 100644 --- a/src/UI/Wikipedia/WikipediaPanel.svelte +++ b/src/UI/Wikipedia/WikipediaPanel.svelte @@ -31,9 +31,9 @@ {:else} - + {#each _wikipediaStores as store (store.tag)} - (selected ? "tab-selected" : "tab-unselected")}> + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> {/each} @@ -51,14 +51,5 @@ diff --git a/src/Utils.ts b/src/Utils.ts index c4f203b6d..123818e2c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1378,66 +1378,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } element.scrollIntoView({ behavior: "smooth", block: "nearest" }) } - - /** - * Returns true if the contents of `a` are the same (and in the same order) as `b`. - * Might have false negatives in some cases - * @param a - * @param b - */ - public static sameList(a: ReadonlyArray, b: ReadonlyArray) { - if (a == b) { - return true - } - if (a === undefined || a === null || b === undefined || b === null) { - return false - } - if (a.length !== b.length) { - return false - } - for (let i = 0; i < a.length; i++) { - const ai = a[i] - const bi = b[i] - if (ai == bi) { - continue - } - if (ai === bi) { - continue - } - return false - } - return true - } - - public static SameObject(a: T, b: T, ignoreKeys?: string[]): boolean { - if (a === b) { - return true - } - if (a === undefined || a === null || b === null || b === undefined) { - return false - } - if (typeof a === "object" && typeof b === "object") { - for (const aKey in a) { - if (!(aKey in b)) { - return false - } - } - - for (const bKey in b) { - if (!(bKey in a)) { - return false - } - } - for (const k in a) { - if (!Utils.SameObject(a[k], b[k])) { - return false - } - } - return true - } - return false - } - /** * * Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}] @@ -1510,45 +1450,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be filename: path.substring(path.lastIndexOf("/") + 1), } } - - /** - * Removes accents from a string - * @param str - * @constructor - * - * Utils.RemoveDiacritics("bâtiments") // => "batiments" - * Utils.RemoveDiacritics(undefined) // => undefined - */ - public static RemoveDiacritics(str?: string): string { - // See #1729 - if (!str) { - return str - } - return str.normalize("NFD").replace(/\p{Diacritic}/gu, "") - } - - /** - * Simplifies a string to increase the chance of a match - * @param str - * Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" - * Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" - * Utils.simplifyStringForSearch(undefined) // => undefined - */ - public static simplifyStringForSearch(str: string): string { - return Utils.RemoveDiacritics(str) - ?.toLowerCase() - ?.replace(/[^a-z0-9]/g, "") - } - - public static randomString(length: number): string { - let result = "" - for (let i = 0; i < length; i++) { - const chr = Math.random().toString(36).substr(2, 3) - result += chr - } - return result - } - /** * Recursively rewrites all keys from `+key`, `key+` and `=key` into `key * diff --git a/src/Utils/Lists.ts b/src/Utils/Lists.ts index 3e0b71b57..7d06a395a 100644 --- a/src/Utils/Lists.ts +++ b/src/Utils/Lists.ts @@ -43,12 +43,15 @@ export class Lists { * Elements are returned in the same order as they appear in the lists. * Null/Undefined is returned as is. If an empty array is given, a new empty array will be returned */ - public static dedup(arr: NonNullable): NonNullable + public static dedup(arr: NonNullable>): NonNullable public static dedup(arr: undefined): undefined - public static dedup(arr: string[] | undefined): string[] | undefined - public static dedup(arr: string[]): string[] { - if (arr === undefined || arr === null) { - return arr + public static dedup(arr: ReadonlyArray | undefined): string[] | undefined + public static dedup(arr: ReadonlyArray): string[] { + if (arr === undefined) { + return undefined + } + if (arr === null) { + return null } const newArr = [] for (const string of arr) { @@ -60,8 +63,8 @@ export class Lists { } public static dedupT(arr: ReadonlyArray): T[] - public static dedupT(arr: null): null - public static dedupT(arr: undefined): undefined + public static dedupT(arr: null): null + public static dedupT(arr: undefined): undefined public static dedupT(arr: ReadonlyArray): T[] { if (arr === undefined) { return undefined @@ -160,7 +163,7 @@ export class Lists { * Lists.duplicates(["a", "b","c","b","b"] // => ["b"] * */ - public static duplicates(arr: string[]): string[] { + public static duplicates(arr: ReadonlyArray): string[] { if (arr === undefined) { return undefined } @@ -175,4 +178,34 @@ export class Lists { return Array.from(duplicates) } + /** + * Returns true if the contents of `a` are the same (and in the same order) as `b`. + * Might have false negatives in some cases + * @param a + * @param b + */ + public static sameList(a: ReadonlyArray, b: ReadonlyArray): boolean { + if (a == b) { + return true + } + if (a === undefined || a === null || b === undefined || b === null) { + return false + } + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + const ai = a[i] + const bi = b[i] + if (ai == bi) { + continue + } + if (ai === bi) { + continue + } + return false + } + return true + } + } diff --git a/src/Utils/Objects.ts b/src/Utils/Objects.ts new file mode 100644 index 000000000..1d8b95c15 --- /dev/null +++ b/src/Utils/Objects.ts @@ -0,0 +1,33 @@ +/** + * Various object-related utils + */ +export default class Objects { + public static sameObject(a: T, b: T, ignoreKeys?: string[]): boolean { + if (a === b) { + return true + } + if (a === undefined || a === null || b === null || b === undefined) { + return false + } + if (typeof a === "object" && typeof b === "object") { + for (const aKey in a) { + if (!(aKey in b)) { + return false + } + } + + for (const bKey in b) { + if (!(bKey in a)) { + return false + } + } + for (const k in a) { + if (!Objects.sameObject(a[k], b[k])) { + return false + } + } + return true + } + return false + } +} diff --git a/src/Utils/Strings.ts b/src/Utils/Strings.ts index ae619a3e4..709014115 100644 --- a/src/Utils/Strings.ts +++ b/src/Utils/Strings.ts @@ -21,4 +21,42 @@ export class Strings { public static isEmojiFlag(string: string): boolean { return /[🇦-🇿]{2}/u.test(string) // flags, see https://stackoverflow.com/questions/53360006/detect-with-regex-if-emoji-is-country-flag } + + /** + * Removes accents from a string + * @param str + * @constructor + * + * Strings.removeDiacritics("bâtiments") // => "batiments" + * Strings.removeDiacritics(undefined) // => undefined + */ + public static removeDiacritics(str?: string): string { + // See #1729 + if (!str) { + return str + } + return str.normalize("NFD").replace(/\p{Diacritic}/gu, "") + } + + /** + * Simplifies a string to increase the chance of a match + * @param str + * Strings.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" + * Strings.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" + * Strings.simplifyStringForSearch(undefined) // => undefined + */ + public static simplifyStringForSearch(str: string): string { + return Strings.removeDiacritics(str) + ?.toLowerCase() + ?.replace(/[^a-z0-9]/g, "") + } + + public static randomString(length: number): string { + let result = "" + for (let i = 0; i < length; i++) { + const chr = Math.random().toString(36).substr(2, 3) + result += chr + } + return result + } } diff --git a/src/index.css b/src/index.css index d1dc9a284..2de7f9750 100644 --- a/src/index.css +++ b/src/index.css @@ -293,6 +293,49 @@ button.unstyled, .button-unstyled button { padding: 0; } +/****** Tablist elements *****/ + +.tablist { + margin: 0.25rem; + padding: 0.5rem; + border: 2px dashed var(--button-background-hover); + border-radius: 0.5rem; + display: flex; + justify-content: center; + flex-wrap: wrap; + +} + +.tab { + border: unset; + border-radius: 0; + transition: all; + color: var(--foreground-color); + border-bottom: 2px solid var(--foreground-color); + font-weight: bold; + + margin: 0.25rem; + padding: 0.25rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + +} + +.tab-selected { + opacity: 100%; + background: var(--interactive-background); +} + +/* Actually used, don't remove*/ +.tab-unselected { + background: #00000000 !important; + opacity: 60%; +} + +.tab-unselected:hover { + background: var(--interactive-background); +} + /******* Other input elements ******/ .hover-alert:hover {