From 8a3f7a012d1abfcbd4ab08f079eaba5eb3f89997 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 23:50:27 +0200 Subject: [PATCH] Chore: formatting --- Docs/BuiltinIndex.md | 26 +- Docs/BuiltinLayers.md | 134 ++- Docs/BuiltinQuestions.md | 21 +- Docs/CalculatedTags.md | 12 +- Docs/Layers/all_vending_machine.md | 18 +- Docs/Layers/bench.md | 5 +- Docs/Layers/bike_cafe.md | 10 + Docs/Layers/bike_repair_station.md | 18 +- Docs/Layers/cafe_pub.md | 18 +- Docs/Layers/charging_station.md | 18 +- Docs/Layers/charging_station_ebikes.md | 18 +- Docs/Layers/dogfoodb.md | 83 +- Docs/Layers/dogshop.md | 18 +- Docs/Layers/elongated_coin.md | 18 +- Docs/Layers/entrance.md | 18 +- Docs/Layers/fitness_centre.md | 18 +- Docs/Layers/food.md | 83 +- Docs/Layers/friture.md | 83 +- Docs/Layers/hackerspace.md | 18 +- Docs/Layers/indoors.md | 113 ++ Docs/Layers/medical-shops.md | 18 +- Docs/Layers/parking.md | 18 +- Docs/Layers/picnic_table.md | 18 +- Docs/Layers/railway_platforms.md | 18 +- Docs/Layers/reception_desk.md | 18 +- Docs/Layers/school.md | 2 + Docs/Layers/shops.md | 18 +- .../Layers/shops_with_climbing_shoe_repair.md | 18 +- Docs/Layers/shower.md | 18 +- Docs/Layers/sport_shops.md | 18 +- Docs/Layers/surveillance_camera.md | 18 + Docs/Layers/ticket_machine.md | 18 +- Docs/Layers/ticket_validator.md | 18 +- Docs/Layers/toilet.md | 18 +- Docs/Layers/toilet_at_amenity.md | 18 +- Docs/Layers/vending_machine.md | 18 +- Docs/SpecialRenderings.md | 50 +- Docs/TagInfo/mapcomplete_benches.json | 7 +- Docs/TagInfo/mapcomplete_food.json | 58 +- Docs/TagInfo/mapcomplete_fritures.json | 58 +- Docs/TagInfo/mapcomplete_indoors.json | 152 +++ Docs/TagInfo/mapcomplete_nature.json | 7 +- Docs/TagInfo/mapcomplete_onwheels.json | 58 +- Docs/TagInfo/mapcomplete_personal.json | 227 +++- Docs/TagInfo/mapcomplete_pets.json | 58 +- Docs/TagInfo/mapcomplete_playgrounds.json | 7 +- Docs/TagInfo/mapcomplete_surveillance.json | 10 + Docs/URL_Parameters.md | 2 +- .../charging_station/charging_station.json | 2 +- .../layers/questions/dogs_allowed.svg.license | 2 +- .../layers/questions/dogs_leashed.svg.license | 2 +- assets/svg/mangrove_logo.svg.license | 2 + package-lock.json | 16 +- package.json | 1 - .../Actors/PreferredRasterLayerSelector.ts | 76 +- .../ImageProviders/ImageUploadManager.ts | 280 ++--- src/Logic/ImageProviders/ImageUploader.ts | 4 +- src/Logic/ImageProviders/Imgur.ts | 19 +- src/Logic/Osm/Actions/LinkImageAction.ts | 37 +- src/Logic/Osm/ChangesetHandler.ts | 9 +- src/Logic/Osm/OsmConnection.ts | 1030 ++++++++--------- src/Logic/State/GeoLocationState.ts | 93 +- src/Logic/State/UserRelatedState.ts | 70 +- src/Logic/State/UserSettingsMetaTagging.ts | 48 +- src/Logic/Web/MangroveReviews.ts | 201 ++-- src/Models/RasterLayers.ts | 174 +-- .../Conversion/LegacyJsonConvert.ts | 4 +- src/UI/Base/Checkbox.svelte | 9 +- src/UI/Base/FileSelector.svelte | 70 +- src/UI/Base/FromHtml.svelte | 2 +- src/UI/Base/Loading.svelte | 10 +- src/UI/BigComponents/Filterview.svelte | 34 +- src/UI/BigComponents/ThemeIntroPanel.svelte | 78 +- src/UI/Image/UploadImage.svelte | 96 +- src/UI/Image/UploadingImageCounter.svelte | 60 +- src/UI/Map/MapLibreAdaptor.ts | 6 +- src/UI/PlantNet/PlantNet.svelte | 125 +- src/UI/PlantNet/PlantNetSpeciesList.svelte | 39 +- src/UI/PlantNet/SpeciesButton.svelte | 89 +- src/UI/Popup/AddNewPoint/AddNewPoint.svelte | 184 +-- src/UI/Popup/CreateNewNote.svelte | 2 +- src/UI/Popup/LinkableImage.svelte | 18 +- src/UI/Reviews/AllReviews.svelte | 53 +- src/UI/Reviews/ReviewForm.svelte | 101 +- src/UI/Reviews/SingleReview.svelte | 38 +- src/UI/Reviews/StarElement.svelte | 32 +- src/UI/Reviews/StarsBar.svelte | 22 +- src/UI/Reviews/StarsBarIcon.svelte | 7 +- src/UI/SpecialVisualization.ts | 191 +-- src/UI/SpecialVisualizations.ts | 197 ++-- src/UI/StylesheetTestGui.svelte | 2 +- src/UI/Wikipedia/WikipediaArticle.svelte | 34 +- src/UI/Wikipedia/WikipediaPanel.svelte | 2 +- src/assets/contributors.json | 4 +- src/assets/language_native.json | 5 +- src/assets/language_translations.json | 184 +-- src/assets/translators.json | 6 +- 97 files changed, 3350 insertions(+), 2136 deletions(-) create mode 100644 assets/svg/mangrove_logo.svg.license diff --git a/Docs/BuiltinIndex.md b/Docs/BuiltinIndex.md index 5e7f0dd77..75a5ccafb 100644 --- a/Docs/BuiltinIndex.md +++ b/Docs/BuiltinIndex.md @@ -55,7 +55,9 @@ + [minimap](#minimap) + [mastodon](#mastodon) + [contact](#contact) + + [etymology.wikipedia-etymology](#etymologywikipedia-etymology) + [denominations-notes](#denominations-notes) + + [single_level](#single_level) + [survey_date](#survey_date) + [id_presets.shop_types](#id_presetsshop_types) + [school.capacity](#schoolcapacity) @@ -442,9 +444,9 @@ - fitness_centre - food - hackerspace + - indoors - parking - picnic_table - - questions - railway_platforms - reception_desk - shops @@ -858,6 +860,17 @@ +### etymology.wikipedia-etymology + + + + + + - indoors + + + + ### denominations-notes @@ -871,6 +884,17 @@ +### single_level + + + + + + - questions + + + + ### survey_date diff --git a/Docs/BuiltinLayers.md b/Docs/BuiltinLayers.md index 15c328651..17eb11d08 100644 --- a/Docs/BuiltinLayers.md +++ b/Docs/BuiltinLayers.md @@ -61,9 +61,6 @@ + [just_created](#just_created) + [leftover-questions](#leftover-questions) + [all-tags](#all-tags) -1. [matchpoint](#matchpoint) - - [Basic tags for this layer](#basic-tags-for-this-layer) - - [Supported attributes](#supported-attributes) 1. [import_candidate](#import_candidate) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) @@ -79,9 +76,13 @@ + [inbox](#inbox) + [settings-link](#settings-link) + [logout](#logout) + + [background-layer-readonly](#background-layer-readonly) + + [background-layer](#background-layer) + [picture-license](#picture-license) + [show_tags](#show_tags) + [all-questions-at-once](#all-questions-at-once) + + [fixate-north](#fixate-north) + + [mangrove-keys](#mangrove-keys) + [translations-title](#translations-title) + [translation-mode](#translation-mode) + [translation-help](#translation-help) @@ -120,7 +121,6 @@ MapComplete has a few data layers available in the theme which have special prop - [split_point](#split_point) - [split_road](#split_road) - [current_view](#current_view) - - [matchpoint](#matchpoint) - [import_candidate](#import_candidate) - [usersettings](#usersettings) @@ -476,7 +476,9 @@ Meta-layer, simply showing a bbox in red - This layer is shown at zoomlevel **0** and higher - **This layer is included automatically in every theme. This layer might contain no points** + - This layer is not visible by default and must be enabled in the filter by the user. - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. + - This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-=true - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` @@ -863,47 +865,6 @@ This tagrendering has no question and is thus read-only - matchpoint -============ - - - - - -The default rendering for a locationInput which snaps onto another object - - - - - - - - This layer is shown at zoomlevel **0** and higher - - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - - - - - Supported attributes ----------------------- - - - - - import_candidate ================== @@ -1043,9 +1004,12 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | +[](https://taginfo.openstreetmap.org/keys/__url_parameter_initialized:language#values) [__url_parameter_initialized:language](https://wiki.openstreetmap.org/wiki/Key:__url_parameter_initialized:language) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:__url_parameter_initialized:language%3Dyes) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-preferred-background-layer#values) [mapcomplete-preferred-background-layer](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-preferred-background-layer) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3D) [osm](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dosm) [photo](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dphoto) [map](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dmap) [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3D) [](https://taginfo.openstreetmap.org/keys/mapcomplete-pictures-license#values) [mapcomplete-pictures-license](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-pictures-license) | Multiple choice | [CC0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC0) [CC-BY 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY 4.0) [CC-BY-SA 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY-SA 4.0) [](https://taginfo.openstreetmap.org/keys/mapcomplete-show_tags#values) [mapcomplete-show_tags](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show_tags) | Multiple choice | [no](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dno) [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dyes) [full](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dfull) [](https://taginfo.openstreetmap.org/keys/mapcomplete-show-all-questions#values) [mapcomplete-show-all-questions](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show-all-questions) | Multiple choice | [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dtrue) [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dfalse) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-fixate-north#values) [mapcomplete-fixate-north](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-fixate-north) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-fixate-north%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-fixate-north%3Dyes) [](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dfalse) [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dtrue) [mobile](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dmobile) [](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dyes) [](https://taginfo.openstreetmap.org/keys/_translation_percentage#values) [_translation_percentage](https://wiki.openstreetmap.org/wiki/Key:_translation_percentage) | Multiple choice | [100](https://wiki.openstreetmap.org/wiki/Tag:_translation_percentage%3D100) @@ -1093,6 +1057,11 @@ This tagrendering has no question and is thus read-only + - *The language was set via an URL-parameter and cannot be set by the user.²* corresponds with `__url_parameter_initialized:language=yes` + + + + ### inbox @@ -1103,8 +1072,8 @@ This tagrendering has no question and is thus read-only - - *{link(Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages=0` - - *{link(You have &LBRACE_unreadMessages&RBRACE
Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages>0` + - *{link(Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,,)}* corresponds with `_unreadMessages=0` + - *{link(You have &LBRACE_unreadMessages&RBRACE
Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,,)}* corresponds with `_unreadMessages>0` @@ -1129,6 +1098,39 @@ This tagrendering has no question and is thus read-only +### background-layer-readonly + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_theme:backgroundLayer~.+&mapcomplete-preferred-background-layer~.+&_theme:backgroundLayer!=` + + + +### background-layer + + + +The question is *What background layer should be shown by default?* + + + + + + - *Use the default background layer* corresponds with `` + - *Use OpenStreetMap-carto as default layer* corresponds with `mapcomplete-preferred-background-layer=osm` + - *Use aerial imagery as default background* corresponds with `mapcomplete-preferred-background-layer=photo` + - *Use a non-openstreetmap based map as default background* corresponds with `mapcomplete-preferred-background-layer=map` + - *Use the current background layer ({__current_background}) as default background* corresponds with `mapcomplete-preferred-background-layer=` + - *Use background layer {mapcomplete-preferred-background-layer} as default background* corresponds with `mapcomplete-preferred-background-layer~.+` + - This option cannot be chosen as answer + + + + ### picture-license @@ -1184,6 +1186,32 @@ The question is *Should questions for unknown data fields appear one-by-one or +### fixate-north + + + +The question is *Should north always be up?* + + + + + + - *Allow to rotate the map* corresponds with `` + - *Always keep north pointing up* corresponds with `mapcomplete-fixate-north=yes` + + + + +### mangrove-keys + + + +This tagrendering has no question and is thus read-only + + + + + ### translations-title @@ -1265,8 +1293,8 @@ This tagrendering has no question and is thus read-only - - *A link to your Mastodon-profile has been been found: {_mastodon_link}* corresponds with `_mastodon_link~.+` - - *We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href="{_mastodon_candidate}" rel="me">Mastodon</a>* corresponds with `_mastodon_candidate~.+` + - *A link to your Mastodon-profile has been been found: {_mastodon_link}* corresponds with `_mastodon_link~.+` + - *We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href="{_mastodon_candidate}" rel="me">Mastodon</a>* corresponds with `_mastodon_candidate~.+` @@ -1408,6 +1436,7 @@ The following layers are included in MapComplete: - [dogpark](./Layers/dogpark.md) - [drinking_water](./Layers/drinking_water.md) - [elevator](./Layers/elevator.md) + - [elongated_coin](./Layers/elongated_coin.md) - [entrance](./Layers/entrance.md) - [etymology](./Layers/etymology.md) - [extinguisher](./Layers/extinguisher.md) @@ -1438,8 +1467,8 @@ The following layers are included in MapComplete: - [map](./Layers/map.md) - [maproulette](./Layers/maproulette.md) - [maproulette_challenge](./Layers/maproulette_challenge.md) - - [matchpoint](./Layers/matchpoint.md) - [maxspeed](./Layers/maxspeed.md) + - [memorial](./Layers/memorial.md) - [named_streets](./Layers/named_streets.md) - [nature_reserve](./Layers/nature_reserve.md) - [note](./Layers/note.md) @@ -1458,6 +1487,7 @@ The following layers are included in MapComplete: - [postboxes](./Layers/postboxes.md) - [postoffices](./Layers/postoffices.md) - [public_bookcase](./Layers/public_bookcase.md) + - [questions](./Layers/questions.md) - [railway_platforms](./Layers/railway_platforms.md) - [rainbow_crossings](./Layers/rainbow_crossings.md) - [range](./Layers/range.md) @@ -1467,6 +1497,7 @@ The following layers are included in MapComplete: - [selected_element](./Layers/selected_element.md) - [shelter](./Layers/shelter.md) - [shops](./Layers/shops.md) + - [shower](./Layers/shower.md) - [slow_roads](./Layers/slow_roads.md) - [speed_camera](./Layers/speed_camera.md) - [speed_display](./Layers/speed_display.md) @@ -1487,6 +1518,7 @@ The following layers are included in MapComplete: - [transit_stops](./Layers/transit_stops.md) - [tree_node](./Layers/tree_node.md) - [usersettings](./Layers/usersettings.md) + - [vending_machine](./Layers/vending_machine.md) - [veterinary](./Layers/veterinary.md) - [viewpoint](./Layers/viewpoint.md) - [village_green](./Layers/village_green.md) @@ -1497,4 +1529,4 @@ The following layers are included in MapComplete: - [windturbine](./Layers/windturbine.md) -This document is autogenerated from [Customizations/AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/develop/Customizations/AllKnownLayouts.ts) +This document is autogenerated from [src/Customizations/AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/develop/src/Customizations/AllKnownLayouts.ts) diff --git a/Docs/BuiltinQuestions.md b/Docs/BuiltinQuestions.md index 10b886651..7b0f907da 100644 --- a/Docs/BuiltinQuestions.md +++ b/Docs/BuiltinQuestions.md @@ -46,7 +46,8 @@ Special library layer which does not need a '.questions'-prefix before being imp + [all_tags](#all_tags) + [just_created](#just_created) + [multilevels](#multilevels) - + [level](#level) + + [repeated](#repeated) + + [single_level](#single_level) + [smoking](#smoking) + [induction-loop](#induction-loop) + [internet](#internet) @@ -655,7 +656,21 @@ This is rendered with `This elevator goes to floors {level}` -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -678,6 +693,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### smoking diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index 4617eca58..d23c29204 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -198,7 +198,7 @@ Adds the geometry type as property. This is identical to the GoeJson geometry ty -Extract the 'level'-tag into a normalized, ';'-separated value +Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`. @@ -260,7 +260,9 @@ To enable this feature, add a field `calculatedTags` in the layer object, e.g.: "calculatedTags": [ - "_someKey=javascript-expression", + "_someKey=javascript-expression (lazy execution)", + + "_some_other_key:=javascript expression (strict execution) "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", @@ -272,6 +274,12 @@ To enable this feature, add a field `calculatedTags` in the layer object, e.g.: +By using `:=` as separator, the attribute will be calculated as soone as the data is loaded (strict evaluation) + +The default behaviour, using `=` as separator, is lazy loading + + + The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object: diff --git a/Docs/Layers/all_vending_machine.md b/Docs/Layers/all_vending_machine.md index 323ec9fc5..469cc7e90 100644 --- a/Docs/Layers/all_vending_machine.md +++ b/Docs/Layers/all_vending_machine.md @@ -269,7 +269,21 @@ The question is *Is this vending machine indoors?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -292,6 +306,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### phone diff --git a/Docs/Layers/bench.md b/Docs/Layers/bench.md index 5d588d8e8..e9869f60e 100644 --- a/Docs/Layers/bench.md +++ b/Docs/Layers/bench.md @@ -56,7 +56,6 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/colour#values) [colour](https://wiki.openstreetmap.org/wiki/Key:colour) | [color](../SpecialInputElements.md#color) | [brown](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dbrown) [green](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgreen) [gray](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgray) [white](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dwhite) [red](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dred) [black](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblack) [blue](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblue) [yellow](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dyellow) [](https://taginfo.openstreetmap.org/keys/survey:date#values) [survey:date](https://wiki.openstreetmap.org/wiki/Key:survey:date) | [date](../SpecialInputElements.md#date) | [](https://wiki.openstreetmap.org/wiki/Tag:survey:date%3D) [](https://taginfo.openstreetmap.org/keys/inscription#values) [inscription](https://wiki.openstreetmap.org/wiki/Key:inscription) | [text](../SpecialInputElements.md#text) | -[](https://taginfo.openstreetmap.org/keys/tourism#values) [tourism](https://wiki.openstreetmap.org/wiki/Key:tourism) | Multiple choice | [artwork](https://wiki.openstreetmap.org/wiki/Tag:tourism%3Dartwork) [](https://wiki.openstreetmap.org/wiki/Tag:tourism%3D) [](https://taginfo.openstreetmap.org/keys/historic#values) [historic](https://wiki.openstreetmap.org/wiki/Key:historic) | Multiple choice | [memorial](https://wiki.openstreetmap.org/wiki/Tag:historic%3Dmemorial) [](https://wiki.openstreetmap.org/wiki/Tag:historic%3D) [](https://taginfo.openstreetmap.org/keys/artwork_type#values) [artwork_type](https://wiki.openstreetmap.org/wiki/Key:artwork_type) | [string](../SpecialInputElements.md#string) | [architecture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Darchitecture) [mural](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dmural) [painting](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dpainting) [sculpture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dsculpture) [statue](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstatue) [bust](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dbust) [stone](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstone) [installation](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dinstallation) [graffiti](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dgraffiti) [relief](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Drelief) [azulejo](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dazulejo) [tilework](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dtilework) [woodcarving](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dwoodcarving) [](https://taginfo.openstreetmap.org/keys/artist:wikidata#values) [artist:wikidata](https://wiki.openstreetmap.org/wiki/Key:artist:wikidata) | [wikidata](../SpecialInputElements.md#wikidata) | @@ -263,7 +262,9 @@ The question is *Does this bench have an artistic element?* - *This bench has an integrated artwork* corresponds with `tourism=artwork` - - *This bench does not have an integrated artwork* corresponds with `` + - *This bench does not have an integrated artwork* corresponds with `not:tourism:artwork=yes` + - *This bench probably doesn't have an integrated artwork* corresponds with `` + - This option cannot be chosen as answer diff --git a/Docs/Layers/bike_cafe.md b/Docs/Layers/bike_cafe.md index 791caa5e3..70c265511 100644 --- a/Docs/Layers/bike_cafe.md +++ b/Docs/Layers/bike_cafe.md @@ -254,6 +254,16 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + ### last_edit diff --git a/Docs/Layers/bike_repair_station.md b/Docs/Layers/bike_repair_station.md index 04ffd306f..1c1c2713b 100644 --- a/Docs/Layers/bike_repair_station.md +++ b/Docs/Layers/bike_repair_station.md @@ -324,7 +324,21 @@ This tagrendering is only visible in the popup if the following condition is met -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -347,6 +361,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### leftover-questions diff --git a/Docs/Layers/cafe_pub.md b/Docs/Layers/cafe_pub.md index 3e2ed5f32..e2b400d5a 100644 --- a/Docs/Layers/cafe_pub.md +++ b/Docs/Layers/cafe_pub.md @@ -97,7 +97,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -120,6 +134,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### Name diff --git a/Docs/Layers/charging_station.md b/Docs/Layers/charging_station.md index 4bbc83000..100e02f9b 100644 --- a/Docs/Layers/charging_station.md +++ b/Docs/Layers/charging_station.md @@ -1933,7 +1933,21 @@ This is rendered with `More info on {website}` -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -1956,6 +1970,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### ref diff --git a/Docs/Layers/charging_station_ebikes.md b/Docs/Layers/charging_station_ebikes.md index 942a5ce08..98e83f35e 100644 --- a/Docs/Layers/charging_station_ebikes.md +++ b/Docs/Layers/charging_station_ebikes.md @@ -1931,7 +1931,21 @@ This is rendered with `More info on {website}` -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -1954,6 +1968,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### ref diff --git a/Docs/Layers/dogfoodb.md b/Docs/Layers/dogfoodb.md index 159eb7f82..1411d582e 100644 --- a/Docs/Layers/dogfoodb.md +++ b/Docs/Layers/dogfoodb.md @@ -46,13 +46,13 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | -[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | +[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) @@ -107,31 +107,6 @@ This tagrendering has no question and is thus read-only -### level - - - -The question is *On what level is this feature located?* - -This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) - -This is rendered with `Located on the {level}th floor` - - - - - - - *Located underground* corresponds with `location=underground` - - This option cannot be chosen as answer - - *Located on the ground floor* corresponds with `level=0` - - *Located on the ground floor* corresponds with `` - - This option cannot be chosen as answer - - *Located on the first floor* corresponds with `level=1` - - *Located on the first basement level* corresponds with `level=-1` - - - - ### Name @@ -262,6 +237,47 @@ The question is *Which methods of payment are accepted here?* +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level + + + +The question is *On what level is this feature located?* + +This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) + +This is rendered with `Located on the {level}th floor` + + + + + + - *Located underground* corresponds with `location=underground` + - This option cannot be chosen as answer + - *Located on the ground floor* corresponds with `level=0` + - *Located on the ground floor* corresponds with `` + - This option cannot be chosen as answer + - *Located on the first floor* corresponds with `level=1` + - *Located on the first basement level* corresponds with `level=-1` + + +This tagrendering has labels `level` + + + ### wheelchair-access @@ -727,7 +743,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\ id | question | osmTags ---- | ---------- | --------- -food-category.0 | Has a vegetarian menu (default) | +food-category.0 | Restaurants and fast food businesses (default) | food-category.1 | Only fastfood businesses | amenity=fast_food food-category.2 | Only restaurants | amenity=restaurant @@ -736,14 +752,14 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only +vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only id | question | osmTags ---- | ---------- | --------- -vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only +vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only @@ -772,6 +788,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes id | question | osmTags ---- | ---------- | --------- accepts_cards.0 | Accepts payment cards | payment:cards=yes + + + + +id | question | osmTags +---- | ---------- | --------- +dogs.0 | No preference towards dogs (default) | +dogs.1 | Dogs allowed | dog=unleashed\|dog=yes +dogs.2 | No dogs allowed | dog=no This document is autogenerated from [assets/themes/pets/pets.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/pets/pets.json) diff --git a/Docs/Layers/dogshop.md b/Docs/Layers/dogshop.md index 4018d415b..8576e9557 100644 --- a/Docs/Layers/dogshop.md +++ b/Docs/Layers/dogshop.md @@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### copyshop-print-sizes diff --git a/Docs/Layers/elongated_coin.md b/Docs/Layers/elongated_coin.md index 8c0a80cbe..067b778d4 100644 --- a/Docs/Layers/elongated_coin.md +++ b/Docs/Layers/elongated_coin.md @@ -297,7 +297,21 @@ The question is *Is the penny press indoors?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -320,6 +334,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### check_date diff --git a/Docs/Layers/entrance.md b/Docs/Layers/entrance.md index b04e700bb..88c0bc060 100644 --- a/Docs/Layers/entrance.md +++ b/Docs/Layers/entrance.md @@ -93,7 +93,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -116,6 +130,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### Entrance type diff --git a/Docs/Layers/fitness_centre.md b/Docs/Layers/fitness_centre.md index be4b04f6a..9d6483660 100644 --- a/Docs/Layers/fitness_centre.md +++ b/Docs/Layers/fitness_centre.md @@ -206,7 +206,21 @@ The question is *Is this place accessible with a wheelchair?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -229,6 +243,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### reviews diff --git a/Docs/Layers/food.md b/Docs/Layers/food.md index 90c4b597d..9f36f751e 100644 --- a/Docs/Layers/food.md +++ b/Docs/Layers/food.md @@ -50,13 +50,13 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | -[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | +[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) @@ -111,31 +111,6 @@ This tagrendering has no question and is thus read-only -### level - - - -The question is *On what level is this feature located?* - -This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) - -This is rendered with `Located on the {level}th floor` - - - - - - - *Located underground* corresponds with `location=underground` - - This option cannot be chosen as answer - - *Located on the ground floor* corresponds with `level=0` - - *Located on the ground floor* corresponds with `` - - This option cannot be chosen as answer - - *Located on the first floor* corresponds with `level=1` - - *Located on the first basement level* corresponds with `level=-1` - - - - ### Name @@ -266,6 +241,47 @@ The question is *Which methods of payment are accepted here?* +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level + + + +The question is *On what level is this feature located?* + +This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) + +This is rendered with `Located on the {level}th floor` + + + + + + - *Located underground* corresponds with `location=underground` + - This option cannot be chosen as answer + - *Located on the ground floor* corresponds with `level=0` + - *Located on the ground floor* corresponds with `` + - This option cannot be chosen as answer + - *Located on the first floor* corresponds with `level=1` + - *Located on the first basement level* corresponds with `level=-1` + + +This tagrendering has labels `level` + + + ### wheelchair-access @@ -731,7 +747,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\ id | question | osmTags ---- | ---------- | --------- -food-category.0 | Has a vegetarian menu (default) | +food-category.0 | Restaurants and fast food businesses (default) | food-category.1 | Only fastfood businesses | amenity=fast_food food-category.2 | Only restaurants | amenity=restaurant @@ -740,14 +756,14 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only +vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only id | question | osmTags ---- | ---------- | --------- -vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only +vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only @@ -776,6 +792,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes id | question | osmTags ---- | ---------- | --------- accepts_cards.0 | Accepts payment cards | payment:cards=yes + + + + +id | question | osmTags +---- | ---------- | --------- +dogs.0 | No preference towards dogs (default) | +dogs.1 | Dogs allowed | dog=unleashed\|dog=yes +dogs.2 | No dogs allowed | dog=no This document is autogenerated from [assets/layers/food/food.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/food/food.json) diff --git a/Docs/Layers/friture.md b/Docs/Layers/friture.md index 8d326c30f..7c61831c8 100644 --- a/Docs/Layers/friture.md +++ b/Docs/Layers/friture.md @@ -46,13 +46,13 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | -[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | +[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) @@ -107,31 +107,6 @@ This tagrendering has no question and is thus read-only -### level - - - -The question is *On what level is this feature located?* - -This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) - -This is rendered with `Located on the {level}th floor` - - - - - - - *Located underground* corresponds with `location=underground` - - This option cannot be chosen as answer - - *Located on the ground floor* corresponds with `level=0` - - *Located on the ground floor* corresponds with `` - - This option cannot be chosen as answer - - *Located on the first floor* corresponds with `level=1` - - *Located on the first basement level* corresponds with `level=-1` - - - - ### Name @@ -262,6 +237,47 @@ The question is *Which methods of payment are accepted here?* +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level + + + +The question is *On what level is this feature located?* + +This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) + +This is rendered with `Located on the {level}th floor` + + + + + + - *Located underground* corresponds with `location=underground` + - This option cannot be chosen as answer + - *Located on the ground floor* corresponds with `level=0` + - *Located on the ground floor* corresponds with `` + - This option cannot be chosen as answer + - *Located on the first floor* corresponds with `level=1` + - *Located on the first basement level* corresponds with `level=-1` + + +This tagrendering has labels `level` + + + ### wheelchair-access @@ -727,7 +743,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\ id | question | osmTags ---- | ---------- | --------- -food-category.0 | Has a vegetarian menu (default) | +food-category.0 | Restaurants and fast food businesses (default) | food-category.1 | Only fastfood businesses | amenity=fast_food food-category.2 | Only restaurants | amenity=restaurant @@ -736,14 +752,14 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only +vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only id | question | osmTags ---- | ---------- | --------- -vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only +vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only @@ -772,6 +788,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes id | question | osmTags ---- | ---------- | --------- accepts_cards.0 | Accepts payment cards | payment:cards=yes + + + + +id | question | osmTags +---- | ---------- | --------- +dogs.0 | No preference towards dogs (default) | +dogs.1 | Dogs allowed | dog=unleashed\|dog=yes +dogs.2 | No dogs allowed | dog=no This document is autogenerated from [assets/themes/fritures/fritures.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/fritures/fritures.json) diff --git a/Docs/Layers/hackerspace.md b/Docs/Layers/hackerspace.md index 3daf2b5ef..dcf2e7d37 100644 --- a/Docs/Layers/hackerspace.md +++ b/Docs/Layers/hackerspace.md @@ -126,7 +126,21 @@ This is rendered with `This hackerspace is named {name}` -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -149,6 +163,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### website diff --git a/Docs/Layers/indoors.md b/Docs/Layers/indoors.md index 13365ac85..25feafe57 100644 --- a/Docs/Layers/indoors.md +++ b/Docs/Layers/indoors.md @@ -49,8 +49,12 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | +[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/ref#values) [ref](https://wiki.openstreetmap.org/wiki/Key:ref) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | +[](https://taginfo.openstreetmap.org/keys/room#values) [room](https://wiki.openstreetmap.org/wiki/Key:room) | Multiple choice | [administration](https://wiki.openstreetmap.org/wiki/Tag:room%3Dadministration) [auditorium](https://wiki.openstreetmap.org/wiki/Tag:room%3Dauditorium) [bedroom](https://wiki.openstreetmap.org/wiki/Tag:room%3Dbedroom) [chapel](https://wiki.openstreetmap.org/wiki/Tag:room%3Dchapel) [class](https://wiki.openstreetmap.org/wiki/Tag:room%3Dclass) [computer](https://wiki.openstreetmap.org/wiki/Tag:room%3Dcomputer) [conference](https://wiki.openstreetmap.org/wiki/Tag:room%3Dconference) [crypt](https://wiki.openstreetmap.org/wiki/Tag:room%3Dcrypt) [kitchen](https://wiki.openstreetmap.org/wiki/Tag:room%3Dkitchen) [laboratory](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlaboratory) [library](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlibrary) [locker](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlocker) [nursery](https://wiki.openstreetmap.org/wiki/Tag:room%3Dnursery) [office](https://wiki.openstreetmap.org/wiki/Tag:room%3Doffice) [prison_cell](https://wiki.openstreetmap.org/wiki/Tag:room%3Dprison_cell) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:room%3Drestaurant) [security_check](https://wiki.openstreetmap.org/wiki/Tag:room%3Dsecurity_check) [sport](https://wiki.openstreetmap.org/wiki/Tag:room%3Dsport) [storage](https://wiki.openstreetmap.org/wiki/Tag:room%3Dstorage) [technical](https://wiki.openstreetmap.org/wiki/Tag:room%3Dtechnical) [toilets](https://wiki.openstreetmap.org/wiki/Tag:room%3Dtoilets) [waiting](https://wiki.openstreetmap.org/wiki/Tag:room%3Dwaiting) +[](https://taginfo.openstreetmap.org/keys/capacity#values) [capacity](https://wiki.openstreetmap.org/wiki/Key:capacity) | [pnat](../SpecialInputElements.md#pnat) | +[](https://taginfo.openstreetmap.org/keys/name:etymology:wikidata#values) [name:etymology:wikidata](https://wiki.openstreetmap.org/wiki/Key:name:etymology:wikidata) | [wikidata](../SpecialInputElements.md#wikidata) | @@ -86,6 +90,47 @@ This tagrendering has no question and is thus read-only +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level + + + +The question is *On what level is this feature located?* + +This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) + +This is rendered with `Located on the {level}th floor` + + + + + + - *Located underground* corresponds with `location=underground` + - This option cannot be chosen as answer + - *Located on the ground floor* corresponds with `level=0` + - *Located on the ground floor* corresponds with `` + - This option cannot be chosen as answer + - *Located on the first floor* corresponds with `level=1` + - *Located on the first basement level* corresponds with `level=-1` + + +This tagrendering has labels `level` + + + ### ref @@ -118,6 +163,74 @@ This tagrendering is only visible in the popup if the following condition is met +### room-type + + + +The question is *What type of room is this?* + + + + + + - *This is a administrative room* corresponds with `room=administration` + - *This is a auditorium* corresponds with `room=auditorium` + - *This is a bedroom* corresponds with `room=bedroom` + - *This is a chapel* corresponds with `room=chapel` + - *This is a classroom* corresponds with `room=class` + - *This is a classroom* corresponds with `room=classroom` + - This option cannot be chosen as answer + - *This is a computer room* corresponds with `room=computer` + - *This is a conference room* corresponds with `room=conference` + - *This is a crypt* corresponds with `room=crypt` + - *This is a kitchen* corresponds with `room=kitchen` + - *This is a laboratory* corresponds with `room=laboratory` + - *This is a library* corresponds with `room=library` + - *This is a locker room* corresponds with `room=locker` + - *This is a nursery* corresponds with `room=nursery` + - *This is an office* corresponds with `room=office` + - *This is a prison_cell* corresponds with `room=prison_cell` + - *This is a restaurant* corresponds with `room=restaurant` + - *This is a room to perform security checks* corresponds with `room=security_check` + - *This is a sport room* corresponds with `room=sport` + - *This is a storage room* corresponds with `room=storage` + - *This is a technical room* corresponds with `room=technical` + - *These are toilets* corresponds with `room=toilets` + - *This is a waiting room* corresponds with `room=waiting` + + + + +### room-capacity + + + +The question is *How much people can at most fit in this room?* + +This rendering asks information about the property [capacity](https://wiki.openstreetmap.org/wiki/Key:capacity) + +This is rendered with `At most {capacity} people fit this room` + + + +This tagrendering is only visible in the popup if the following condition is met: `room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom` + + + +### wikipedia-etymology + + + +The question is *What is the Wikidata-item that this object is named after?* + +This rendering asks information about the property [name:etymology:wikidata](https://wiki.openstreetmap.org/wiki/Key:name:etymology:wikidata) + +This is rendered with `

Wikipedia article of the name giver

{wikipedia(name:etymology:wikidata):max-height:20rem}` + + + + + ### leftover-questions diff --git a/Docs/Layers/medical-shops.md b/Docs/Layers/medical-shops.md index 36e0bce0d..2464db22e 100644 --- a/Docs/Layers/medical-shops.md +++ b/Docs/Layers/medical-shops.md @@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### copyshop-print-sizes diff --git a/Docs/Layers/parking.md b/Docs/Layers/parking.md index 1fcc04a39..6856d586d 100644 --- a/Docs/Layers/parking.md +++ b/Docs/Layers/parking.md @@ -88,7 +88,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -111,6 +125,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### parking-type diff --git a/Docs/Layers/picnic_table.md b/Docs/Layers/picnic_table.md index 807abbcd1..089627951 100644 --- a/Docs/Layers/picnic_table.md +++ b/Docs/Layers/picnic_table.md @@ -86,7 +86,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -109,6 +123,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### picnic_table-material diff --git a/Docs/Layers/railway_platforms.md b/Docs/Layers/railway_platforms.md index 5dc7387d1..9e9a075d8 100644 --- a/Docs/Layers/railway_platforms.md +++ b/Docs/Layers/railway_platforms.md @@ -74,7 +74,21 @@ This is rendered with `Platform {ref}` -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -97,6 +111,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### leftover-questions diff --git a/Docs/Layers/reception_desk.md b/Docs/Layers/reception_desk.md index f78d8e3e0..be0164aa8 100644 --- a/Docs/Layers/reception_desk.md +++ b/Docs/Layers/reception_desk.md @@ -85,7 +85,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -108,6 +122,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### desk-height diff --git a/Docs/Layers/school.md b/Docs/Layers/school.md index 1841a44f6..69d9c4859 100644 --- a/Docs/Layers/school.md +++ b/Docs/Layers/school.md @@ -15,6 +15,8 @@ Schools giving primary and secondary education and post-secondary, non-tertiary - This layer is shown at zoomlevel **12** and higher + - This layer will automatically load [school](./school.md) into the layout as it depends on it: a calculated tag loads features from this layer (calculatedTag[0] which calculates the value for _enclosing) + - This layer is needed as dependency for layer [school](#school) diff --git a/Docs/Layers/shops.md b/Docs/Layers/shops.md index 551096faf..557b58c58 100644 --- a/Docs/Layers/shops.md +++ b/Docs/Layers/shops.md @@ -389,7 +389,21 @@ The question is *Which methods of payment are accepted here?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -412,6 +426,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### copyshop-print-sizes diff --git a/Docs/Layers/shops_with_climbing_shoe_repair.md b/Docs/Layers/shops_with_climbing_shoe_repair.md index 879540277..0ee5662e7 100644 --- a/Docs/Layers/shops_with_climbing_shoe_repair.md +++ b/Docs/Layers/shops_with_climbing_shoe_repair.md @@ -401,7 +401,21 @@ The question is *Which methods of payment are accepted here?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -424,6 +438,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### copyshop-print-sizes diff --git a/Docs/Layers/shower.md b/Docs/Layers/shower.md index b5551bce7..28edf884c 100644 --- a/Docs/Layers/shower.md +++ b/Docs/Layers/shower.md @@ -88,7 +88,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -111,6 +125,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### access diff --git a/Docs/Layers/sport_shops.md b/Docs/Layers/sport_shops.md index 17c61c236..75cb17865 100644 --- a/Docs/Layers/sport_shops.md +++ b/Docs/Layers/sport_shops.md @@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### copyshop-print-sizes diff --git a/Docs/Layers/surveillance_camera.md b/Docs/Layers/surveillance_camera.md index a03451ad8..d28122081 100644 --- a/Docs/Layers/surveillance_camera.md +++ b/Docs/Layers/surveillance_camera.md @@ -16,6 +16,7 @@ This layer shows surveillance cameras and allows a contributor to update informa - This layer is shown at zoomlevel **12** and higher - This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[1]) + - This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[3]) @@ -48,6 +49,7 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | +[](https://taginfo.openstreetmap.org/keys/surveillance:type#values) [surveillance:type](https://wiki.openstreetmap.org/wiki/Key:surveillance:type) | Multiple choice | [camera](https://wiki.openstreetmap.org/wiki/Tag:surveillance:type%3Dcamera) [ALPR](https://wiki.openstreetmap.org/wiki/Tag:surveillance:type%3DALPR) [](https://taginfo.openstreetmap.org/keys/camera:type#values) [camera:type](https://wiki.openstreetmap.org/wiki/Key:camera:type) | Multiple choice | [fixed](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dfixed) [dome](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Ddome) [panning](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dpanning) [](https://taginfo.openstreetmap.org/keys/camera:direction#values) [camera:direction](https://wiki.openstreetmap.org/wiki/Key:camera:direction) | [direction](../SpecialInputElements.md#direction) | [](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | @@ -91,6 +93,22 @@ This tagrendering has no question and is thus read-only +### has_alpr + + + +The question is *Can this camera automatically detect license plates?* + + + + + + - *This is a camera without number plate recognition.* corresponds with `surveillance:type=camera` + - *This is an ALPR (Automatic License Plate Reader)* corresponds with `surveillance:type=ALPR` + + + + ### Camera type: fixed; panning; dome diff --git a/Docs/Layers/ticket_machine.md b/Docs/Layers/ticket_machine.md index 50ab62d9b..6c7baebf6 100644 --- a/Docs/Layers/ticket_machine.md +++ b/Docs/Layers/ticket_machine.md @@ -86,7 +86,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -109,6 +123,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### operator diff --git a/Docs/Layers/ticket_validator.md b/Docs/Layers/ticket_validator.md index 1449ab289..6779a03f5 100644 --- a/Docs/Layers/ticket_validator.md +++ b/Docs/Layers/ticket_validator.md @@ -73,7 +73,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -96,6 +110,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### barrier diff --git a/Docs/Layers/toilet.md b/Docs/Layers/toilet.md index c18ea698c..19e914f89 100644 --- a/Docs/Layers/toilet.md +++ b/Docs/Layers/toilet.md @@ -98,7 +98,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -121,6 +135,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### toilet-access diff --git a/Docs/Layers/toilet_at_amenity.md b/Docs/Layers/toilet_at_amenity.md index 761d80e99..0644230f1 100644 --- a/Docs/Layers/toilet_at_amenity.md +++ b/Docs/Layers/toilet_at_amenity.md @@ -95,7 +95,21 @@ This tagrendering has no question and is thus read-only -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -118,6 +132,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### toilet-access diff --git a/Docs/Layers/vending_machine.md b/Docs/Layers/vending_machine.md index 6034c6854..5e5300866 100644 --- a/Docs/Layers/vending_machine.md +++ b/Docs/Layers/vending_machine.md @@ -269,7 +269,21 @@ The question is *Is this vending machine indoors?* -### level +### repeated + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+` + +This tagrendering has labels `level` + + + +### single_level @@ -292,6 +306,8 @@ This is rendered with `Located on the {level}th floor` - *Located on the first basement level* corresponds with `level=-1` +This tagrendering has labels `level` + ### phone diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index 6743c425f..1faec577e 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -110,8 +110,12 @@ In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "ar * [Example usage of image_carousel](#example-usage-of-image_carousel) + [image_upload](#image_upload) * [Example usage of image_upload](#example-usage-of-image_upload) - + [reviews](#reviews) - * [Example usage of reviews](#example-usage-of-reviews) + + [rating](#rating) + * [Example usage of rating](#example-usage-of-rating) + + [create_review](#create_review) + * [Example usage of create_review](#example-usage-of-create_review) + + [list_reviews](#list_reviews) + * [Example usage of list_reviews](#example-usage-of-list_reviews) + [opening_hours_table](#opening_hours_table) * [Example usage of opening_hours_table](#example-usage-of-opening_hours_table) + [live](#live) @@ -744,17 +748,49 @@ image_key | image,mapillary,image,wikidata,wikimedia_commons,image,image | The k name | default | description ------ | --------- | ------------- -image-key | image | Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added) -label | Add image | The text to show on the button +image-key | _undefined_ | Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added) +label | _undefined_ | The text to show on the button #### Example usage of image_upload - `{image_upload(image,Add image)}` + `{image_upload(,)}` -### reviews +### rating + + Shows stars which represent the avarage rating on mangrove.reviews + +name | default | description +------ | --------- | ------------- +subjectKey | name | The key to use to determine the subject. If specified, the subject will be tags[subjectKey] +fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value + + +#### Example usage of rating + + `{rating(name,)}` + + + +### create_review + + Invites the contributor to leave a review. Somewhat small UI-element until interacted + +name | default | description +------ | --------- | ------------- +subjectKey | name | The key to use to determine the subject. If specified, the subject will be tags[subjectKey] +fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value + + +#### Example usage of create_review + + `{create_review(name,)}` + + + +### list_reviews Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten @@ -764,7 +800,7 @@ subjectKey | name | The key to use to determine the subject. If specified, the s fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value -#### Example usage of reviews +#### Example usage of list_reviews `{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used diff --git a/Docs/TagInfo/mapcomplete_benches.json b/Docs/TagInfo/mapcomplete_benches.json index 30ad200de..338a97a45 100644 --- a/Docs/TagInfo/mapcomplete_benches.json +++ b/Docs/TagInfo/mapcomplete_benches.json @@ -247,9 +247,14 @@ "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches')", "value": "artwork" }, + { + "key": "not:tourism:artwork", + "description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches')", + "value": "yes" + }, { "key": "tourism", - "description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches') Picking this answer will delete the key tourism.", + "description": "Layer 'Benches' shows with a fixed text, namely 'This bench probably doesn't have an integrated artwork' (in the mapcomplete.org theme 'Benches') Picking this answer will delete the key tourism.", "value": "" }, { diff --git a/Docs/TagInfo/mapcomplete_food.json b/Docs/TagInfo/mapcomplete_food.json index 315523e95..16e351db3 100644 --- a/Docs/TagInfo/mapcomplete_food.json +++ b/Docs/TagInfo/mapcomplete_food.json @@ -40,35 +40,6 @@ "key": "wikipedia", "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Restaurants and fast food')" - }, - { - "key": "location", - "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Restaurants and fast food')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Restaurants and fast food') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", - "value": "-1" - }, { "key": "name", "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Restaurants and fast food')" @@ -126,6 +97,35 @@ "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", "value": "yes" }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Restaurants and fast food')" + }, + { + "key": "location", + "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Restaurants and fast food')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Restaurants and fast food') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", + "value": "-1" + }, { "key": "wheelchair", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", diff --git a/Docs/TagInfo/mapcomplete_fritures.json b/Docs/TagInfo/mapcomplete_fritures.json index d6e655e01..c3de637cd 100644 --- a/Docs/TagInfo/mapcomplete_fritures.json +++ b/Docs/TagInfo/mapcomplete_fritures.json @@ -44,35 +44,6 @@ "key": "wikipedia", "description": "The layer 'Fries shop allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "level", - "description": "Layer 'Fries shop' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Fries shops')" - }, - { - "key": "location", - "description": "Layer 'Fries shop' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Fries shops')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Fries shop' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Fries shop' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Fries shops') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Fries shop' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Fries shop' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", - "value": "-1" - }, { "key": "name", "description": "Layer 'Fries shop' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Fries shops')" @@ -130,6 +101,35 @@ "description": "Layer 'Fries shop' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", "value": "yes" }, + { + "key": "level", + "description": "Layer 'Fries shop' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Fries shops')" + }, + { + "key": "location", + "description": "Layer 'Fries shop' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Fries shops')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Fries shop' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Fries shop' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Fries shops') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Fries shop' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Fries shop' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", + "value": "-1" + }, { "key": "wheelchair", "description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", diff --git a/Docs/TagInfo/mapcomplete_indoors.json b/Docs/TagInfo/mapcomplete_indoors.json index 594b6fb25..b28e95e76 100644 --- a/Docs/TagInfo/mapcomplete_indoors.json +++ b/Docs/TagInfo/mapcomplete_indoors.json @@ -55,6 +55,35 @@ "key": "wikipedia", "description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "level", + "description": "Layer 'Indoors' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Indoors')" + }, + { + "key": "location", + "description": "Layer 'Indoors' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Indoors')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Indoors') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "-1" + }, { "key": "ref", "description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)" @@ -63,6 +92,129 @@ "key": "name", "description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)" }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=administration with a fixed text, namely 'This is a administrative room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "administration" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=auditorium with a fixed text, namely 'This is a auditorium' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "auditorium" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=bedroom with a fixed text, namely 'This is a bedroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "bedroom" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=chapel with a fixed text, namely 'This is a chapel' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "chapel" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=class with a fixed text, namely 'This is a classroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "class" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=classroom with a fixed text, namely 'This is a classroom' (in the mapcomplete.org theme 'Indoors')", + "value": "classroom" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=computer with a fixed text, namely 'This is a computer room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "computer" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=conference with a fixed text, namely 'This is a conference room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "conference" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=crypt with a fixed text, namely 'This is a crypt' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "crypt" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=kitchen with a fixed text, namely 'This is a kitchen' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "kitchen" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=laboratory with a fixed text, namely 'This is a laboratory' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "laboratory" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=library with a fixed text, namely 'This is a library' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "library" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=locker with a fixed text, namely 'This is a locker room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "locker" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=nursery with a fixed text, namely 'This is a nursery' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "nursery" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=office with a fixed text, namely 'This is an office' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "office" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=prison_cell with a fixed text, namely 'This is a prison_cell' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "prison_cell" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=restaurant with a fixed text, namely 'This is a restaurant' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "restaurant" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=security_check with a fixed text, namely 'This is a room to perform security checks' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "security_check" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=sport with a fixed text, namely 'This is a sport room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "sport" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=storage with a fixed text, namely 'This is a storage room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "storage" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=technical with a fixed text, namely 'This is a technical room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "technical" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=toilets with a fixed text, namely 'These are toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "toilets" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=waiting with a fixed text, namely 'This is a waiting room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "waiting" + }, + { + "key": "capacity", + "description": "Layer 'Indoors' shows and asks freeform values for key 'capacity' (in the mapcomplete.org theme 'Indoors') (This is only shown if room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom)" + }, + { + "key": "name:etymology:wikidata", + "description": "Layer 'Indoors' shows and asks freeform values for key 'name:etymology:wikidata' (in the mapcomplete.org theme 'Indoors') (This is only shown if name:etymology!=unknown)" + }, { "key": "highway", "description": "The MapComplete theme Indoors has a layer Pedestrian paths showing features with this tag", diff --git a/Docs/TagInfo/mapcomplete_nature.json b/Docs/TagInfo/mapcomplete_nature.json index 4fd740c19..d6a050921 100644 --- a/Docs/TagInfo/mapcomplete_nature.json +++ b/Docs/TagInfo/mapcomplete_nature.json @@ -668,9 +668,14 @@ "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature')", "value": "artwork" }, + { + "key": "not:tourism:artwork", + "description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature')", + "value": "yes" + }, { "key": "tourism", - "description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature') Picking this answer will delete the key tourism.", + "description": "Layer 'Benches' shows with a fixed text, namely 'This bench probably doesn't have an integrated artwork' (in the mapcomplete.org theme 'Into nature') Picking this answer will delete the key tourism.", "value": "" }, { diff --git a/Docs/TagInfo/mapcomplete_onwheels.json b/Docs/TagInfo/mapcomplete_onwheels.json index 97ce2f8c7..4b030a3fc 100644 --- a/Docs/TagInfo/mapcomplete_onwheels.json +++ b/Docs/TagInfo/mapcomplete_onwheels.json @@ -550,35 +550,6 @@ "key": "wikipedia", "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'OnWheels')" - }, - { - "key": "location", - "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'OnWheels')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'OnWheels') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", - "value": "-1" - }, { "key": "name", "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'OnWheels')" @@ -636,6 +607,35 @@ "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", "value": "yes" }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'OnWheels')" + }, + { + "key": "location", + "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'OnWheels')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'OnWheels') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "-1" + }, { "key": "wheelchair", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", diff --git a/Docs/TagInfo/mapcomplete_personal.json b/Docs/TagInfo/mapcomplete_personal.json index 06ef64b47..09d70051e 100644 --- a/Docs/TagInfo/mapcomplete_personal.json +++ b/Docs/TagInfo/mapcomplete_personal.json @@ -979,9 +979,14 @@ "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "value": "artwork" }, + { + "key": "not:tourism:artwork", + "description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "yes" + }, { "key": "tourism", - "description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key tourism.", + "description": "Layer 'Benches' shows with a fixed text, namely 'This bench probably doesn't have an integrated artwork' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key tourism.", "value": "" }, { @@ -7305,35 +7310,6 @@ "key": "wikipedia", "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')" - }, - { - "key": "location", - "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", - "value": "-1" - }, { "key": "name", "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme')" @@ -7391,6 +7367,35 @@ "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "value": "yes" }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')" + }, + { + "key": "location", + "description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "-1" + }, { "key": "wheelchair", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", @@ -8383,6 +8388,35 @@ "key": "wikipedia", "description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "level", + "description": "Layer 'Indoors' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')" + }, + { + "key": "location", + "description": "Layer 'Indoors' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Indoors' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "-1" + }, { "key": "ref", "description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)" @@ -8391,6 +8425,129 @@ "key": "name", "description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)" }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=administration with a fixed text, namely 'This is a administrative room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "administration" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=auditorium with a fixed text, namely 'This is a auditorium' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "auditorium" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=bedroom with a fixed text, namely 'This is a bedroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "bedroom" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=chapel with a fixed text, namely 'This is a chapel' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "chapel" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=class with a fixed text, namely 'This is a classroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "class" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=classroom with a fixed text, namely 'This is a classroom' (in the mapcomplete.org theme 'Personal theme')", + "value": "classroom" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=computer with a fixed text, namely 'This is a computer room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "computer" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=conference with a fixed text, namely 'This is a conference room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "conference" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=crypt with a fixed text, namely 'This is a crypt' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "crypt" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=kitchen with a fixed text, namely 'This is a kitchen' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "kitchen" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=laboratory with a fixed text, namely 'This is a laboratory' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "laboratory" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=library with a fixed text, namely 'This is a library' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "library" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=locker with a fixed text, namely 'This is a locker room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "locker" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=nursery with a fixed text, namely 'This is a nursery' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "nursery" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=office with a fixed text, namely 'This is an office' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "office" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=prison_cell with a fixed text, namely 'This is a prison_cell' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "prison_cell" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=restaurant with a fixed text, namely 'This is a restaurant' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "restaurant" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=security_check with a fixed text, namely 'This is a room to perform security checks' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "security_check" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=sport with a fixed text, namely 'This is a sport room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "sport" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=storage with a fixed text, namely 'This is a storage room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "storage" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=technical with a fixed text, namely 'This is a technical room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "technical" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=toilets with a fixed text, namely 'These are toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "toilets" + }, + { + "key": "room", + "description": "Layer 'Indoors' shows room=waiting with a fixed text, namely 'This is a waiting room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "waiting" + }, + { + "key": "capacity", + "description": "Layer 'Indoors' shows and asks freeform values for key 'capacity' (in the mapcomplete.org theme 'Personal theme') (This is only shown if room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom)" + }, + { + "key": "name:etymology:wikidata", + "description": "Layer 'Indoors' shows and asks freeform values for key 'name:etymology:wikidata' (in the mapcomplete.org theme 'Personal theme') (This is only shown if name:etymology!=unknown)" + }, { "key": "information", "description": "The MapComplete theme Personal theme has a layer Information boards showing features with this tag", @@ -12686,6 +12843,16 @@ "key": "wikipedia", "description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "surveillance:type", + "description": "Layer 'Surveillance camera's' shows surveillance:type=camera with a fixed text, namely 'This is a camera without number plate recognition.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "camera" + }, + { + "key": "surveillance:type", + "description": "Layer 'Surveillance camera's' shows surveillance:type=ALPR with a fixed text, namely 'This is an ALPR (Automatic License Plate Reader)' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "ALPR" + }, { "key": "camera:type", "description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", diff --git a/Docs/TagInfo/mapcomplete_pets.json b/Docs/TagInfo/mapcomplete_pets.json index 22b0186f7..16f805568 100644 --- a/Docs/TagInfo/mapcomplete_pets.json +++ b/Docs/TagInfo/mapcomplete_pets.json @@ -114,35 +114,6 @@ "key": "wikipedia", "description": "The layer 'Dog friendly eateries allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "level", - "description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" - }, - { - "key": "location", - "description": "Layer 'Dog friendly eateries' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Dog friendly eateries' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Dog friendly eateries' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Dog friendly eateries' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Dog friendly eateries' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", - "value": "-1" - }, { "key": "name", "description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" @@ -200,6 +171,35 @@ "description": "Layer 'Dog friendly eateries' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", "value": "yes" }, + { + "key": "level", + "description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" + }, + { + "key": "location", + "description": "Layer 'Dog friendly eateries' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Dog friendly eateries' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Dog friendly eateries' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Dog friendly eateries' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Dog friendly eateries' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", + "value": "-1" + }, { "key": "wheelchair", "description": "Layer 'Dog friendly eateries' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", diff --git a/Docs/TagInfo/mapcomplete_playgrounds.json b/Docs/TagInfo/mapcomplete_playgrounds.json index 8f2e64beb..92903d04a 100644 --- a/Docs/TagInfo/mapcomplete_playgrounds.json +++ b/Docs/TagInfo/mapcomplete_playgrounds.json @@ -340,9 +340,14 @@ "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds')", "value": "artwork" }, + { + "key": "not:tourism:artwork", + "description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds')", + "value": "yes" + }, { "key": "tourism", - "description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds') Picking this answer will delete the key tourism.", + "description": "Layer 'Benches' shows with a fixed text, namely 'This bench probably doesn't have an integrated artwork' (in the mapcomplete.org theme 'Playgrounds') Picking this answer will delete the key tourism.", "value": "" }, { diff --git a/Docs/TagInfo/mapcomplete_surveillance.json b/Docs/TagInfo/mapcomplete_surveillance.json index 5648fa720..c81f266a6 100644 --- a/Docs/TagInfo/mapcomplete_surveillance.json +++ b/Docs/TagInfo/mapcomplete_surveillance.json @@ -50,6 +50,16 @@ "key": "wikipedia", "description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "surveillance:type", + "description": "Layer 'Surveillance camera's' shows surveillance:type=camera with a fixed text, namely 'This is a camera without number plate recognition.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')", + "value": "camera" + }, + { + "key": "surveillance:type", + "description": "Layer 'Surveillance camera's' shows surveillance:type=ALPR with a fixed text, namely 'This is an ALPR (Automatic License Plate Reader)' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')", + "value": "ALPR" + }, { "key": "camera:type", "description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')", diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index 7f88f3f50..a4e2da5bc 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -346,7 +346,7 @@ This documentation is defined in the source code at [FeatureSwitchState.ts](/src This documentation is defined in the source code at [FeatureSwitchState.ts](/src/Logic/State/FeatureSwitchState.ts#L199) - The default value is _osm_ + No default value set diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 62361dea1..174b24c58 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -5294,4 +5294,4 @@ }, "neededChangesets": 10 } -} +} \ No newline at end of file diff --git a/assets/layers/questions/dogs_allowed.svg.license b/assets/layers/questions/dogs_allowed.svg.license index e14c126f7..cc80da1dd 100644 --- a/assets/layers/questions/dogs_allowed.svg.license +++ b/assets/layers/questions/dogs_allowed.svg.license @@ -1,2 +1,2 @@ SPDX-FileCopyrightText: OpenClipArt -SPDX-License-Identifier: PD \ No newline at end of file +SPDX-License-Identifier: PUBLIC-DOMAIN \ No newline at end of file diff --git a/assets/layers/questions/dogs_leashed.svg.license b/assets/layers/questions/dogs_leashed.svg.license index e32d67ee6..ae864e1e2 100644 --- a/assets/layers/questions/dogs_leashed.svg.license +++ b/assets/layers/questions/dogs_leashed.svg.license @@ -1,2 +1,2 @@ SPDX-FileCopyrightText: NPS Graphics, converted by User:ZyMOS -SPDX-License-Identifier: PD \ No newline at end of file +SPDX-License-Identifier: PUBLIC-DOMAIN \ No newline at end of file diff --git a/assets/svg/mangrove_logo.svg.license b/assets/svg/mangrove_logo.svg.license new file mode 100644 index 000000000..0f5f50aa6 --- /dev/null +++ b/assets/svg/mangrove_logo.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Mangrove.reviews +SPDX-License-Identifier: LicenseRef-LOGO \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5f6a1ce4f..2c7347c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mapcomplete", - "version": "0.33.1", + "version": "0.33.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mapcomplete", - "version": "0.33.1", + "version": "0.33.5", "license": "GPL-3.0-or-later", "dependencies": { "@rgossiaux/svelte-headlessui": "^1.0.2", @@ -4970,9 +4970,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001541", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz", + "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==", "dev": true, "funding": [ { @@ -17021,9 +17021,9 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001541", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz", + "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==", "dev": true }, "canvg": { diff --git a/package.json b/package.json index bc975ef3d..a70b46f67 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "main": "index.ts", "type": "module", "config": { - "#": "Various endpoints that are instance-specific. This is the default configuration, which is re-exported in 'Constants.ts'.", "#": "Use MAPCOMPLETE_CONFIGURATION to use an additional configuration, e.g. `MAPCOMPLETE_CONFIGURATION=config_hetzner`", "#oauth_credentials:comment": [ "`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.", diff --git a/src/Logic/Actors/PreferredRasterLayerSelector.ts b/src/Logic/Actors/PreferredRasterLayerSelector.ts index 7ed20aa95..a4c38512a 100644 --- a/src/Logic/Actors/PreferredRasterLayerSelector.ts +++ b/src/Logic/Actors/PreferredRasterLayerSelector.ts @@ -1,5 +1,5 @@ -import { Store, UIEventSource } from "../UIEventSource"; -import { RasterLayerPolygon } from "../../Models/RasterLayers"; +import { Store, UIEventSource } from "../UIEventSource" +import { RasterLayerPolygon } from "../../Models/RasterLayers" /** * Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value. @@ -7,40 +7,47 @@ import { RasterLayerPolygon } from "../../Models/RasterLayers"; * It the requested layer is not available, a layer of the same type will be selected. */ export class PreferredRasterLayerSelector { - private readonly _rasterLayerSetting: UIEventSource; - private readonly _availableLayers: Store; - private readonly _preferredBackgroundLayer: UIEventSource; - private readonly _queryParameter: UIEventSource; + private readonly _rasterLayerSetting: UIEventSource + private readonly _availableLayers: Store + private readonly _preferredBackgroundLayer: UIEventSource< + string | "photo" | "map" | "osmbasedmap" | undefined + > + private readonly _queryParameter: UIEventSource - constructor(rasterLayerSetting: UIEventSource, availableLayers: Store, queryParameter: UIEventSource, preferredBackgroundLayer: UIEventSource) { - this._rasterLayerSetting = rasterLayerSetting; - this._availableLayers = availableLayers; - this._queryParameter = queryParameter; - this._preferredBackgroundLayer = preferredBackgroundLayer; - const self = this; + constructor( + rasterLayerSetting: UIEventSource, + availableLayers: Store, + queryParameter: UIEventSource, + preferredBackgroundLayer: UIEventSource< + string | "photo" | "map" | "osmbasedmap" | undefined + > + ) { + this._rasterLayerSetting = rasterLayerSetting + this._availableLayers = availableLayers + this._queryParameter = queryParameter + this._preferredBackgroundLayer = preferredBackgroundLayer + const self = this - this._rasterLayerSetting.addCallbackD(layer => { + this._rasterLayerSetting.addCallbackD((layer) => { if (layer.properties.id !== this._queryParameter.data) { - this._queryParameter.setData(undefined); - return true; + this._queryParameter.setData(undefined) + return true } - }); + }) - - this._queryParameter.addCallbackAndRunD(_ => { - const isApplied = self.updateLayer(); + this._queryParameter.addCallbackAndRunD((_) => { + const isApplied = self.updateLayer() if (!isApplied) { // A different layer was set as background // We remove this queryParameter instead - self._queryParameter.setData(undefined); - return true; // Unregister + self._queryParameter.setData(undefined) + return true // Unregister } - }); + }) - this._preferredBackgroundLayer.addCallbackD(_ => self.updateLayer()); - - this._availableLayers.addCallbackD(_ => self.updateLayer()); + this._preferredBackgroundLayer.addCallbackD((_) => self.updateLayer()) + this._availableLayers.addCallbackD((_) => self.updateLayer()) } /** @@ -48,20 +55,19 @@ export class PreferredRasterLayerSelector { * @private */ private updateLayer() { - // What is the ID of the layer we have to (try to) load? - const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data; - const available = this._availableLayers.data; - const isCategory = targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map" - const foundLayer = isCategory ? available.find(l => l.properties.category === targetLayerId) : available.find(l => l.properties.id === targetLayerId); + const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data + const available = this._availableLayers.data + const isCategory = + targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map" + const foundLayer = isCategory + ? available.find((l) => l.properties.category === targetLayerId) + : available.find((l) => l.properties.id === targetLayerId) if (foundLayer) { - this._rasterLayerSetting.setData(foundLayer); - return true; + this._rasterLayerSetting.setData(foundLayer) + return true } // The current layer is not in view - - } - } diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index 85964a507..a187e82ef 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -1,159 +1,159 @@ -import { ImageUploader } from "./ImageUploader"; -import LinkImageAction from "../Osm/Actions/LinkImageAction"; -import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; -import { OsmId, OsmTags } from "../../Models/OsmFeature"; -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; -import { Store, UIEventSource } from "../UIEventSource"; -import { OsmConnection } from "../Osm/OsmConnection"; -import { Changes } from "../Osm/Changes"; -import Translations from "../../UI/i18n/Translations"; -import NoteCommentElement from "../../UI/Popup/NoteCommentElement"; - +import { ImageUploader } from "./ImageUploader" +import LinkImageAction from "../Osm/Actions/LinkImageAction" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" +import { OsmId, OsmTags } from "../../Models/OsmFeature" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import { Store, UIEventSource } from "../UIEventSource" +import { OsmConnection } from "../Osm/OsmConnection" +import { Changes } from "../Osm/Changes" +import Translations from "../../UI/i18n/Translations" +import NoteCommentElement from "../../UI/Popup/NoteCommentElement" /** * The ImageUploadManager has a */ export class ImageUploadManager { + private readonly _uploader: ImageUploader + private readonly _featureProperties: FeaturePropertiesStore + private readonly _layout: LayoutConfig - private readonly _uploader: ImageUploader; - private readonly _featureProperties: FeaturePropertiesStore; - private readonly _layout: LayoutConfig; + private readonly _uploadStarted: Map> = new Map() + private readonly _uploadFinished: Map> = new Map() + private readonly _uploadFailed: Map> = new Map() + private readonly _uploadRetried: Map> = new Map() + private readonly _uploadRetriedSuccess: Map> = new Map() + private readonly _osmConnection: OsmConnection + private readonly _changes: Changes - private readonly _uploadStarted: Map> = new Map(); - private readonly _uploadFinished: Map> = new Map(); - private readonly _uploadFailed: Map> = new Map(); - private readonly _uploadRetried: Map> = new Map(); - private readonly _uploadRetriedSuccess: Map> = new Map(); - private readonly _osmConnection: OsmConnection; - private readonly _changes: Changes; - - constructor(layout: LayoutConfig, uploader: ImageUploader, featureProperties: FeaturePropertiesStore, osmConnection: OsmConnection, changes: Changes) { - this._uploader = uploader; - this._featureProperties = featureProperties; - this._layout = layout; - this._osmConnection = osmConnection; - this._changes = changes; - } - - /** - * Gets various counters. - * Note that counters can only increase - * If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased - * @param featureId: the id of the feature you want information for. '*' has a global counter - */ - public getCountsFor(featureId: string | "*"): { - retried: Store; - uploadStarted: Store; - retrySuccess: Store; - failed: Store; - uploadFinished: Store - } { - return { - uploadStarted: this.getCounterFor(this._uploadStarted, featureId), - uploadFinished: this.getCounterFor(this._uploadFinished, featureId), - retried: this.getCounterFor(this._uploadRetried, featureId), - failed: this.getCounterFor(this._uploadFailed, featureId), - retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId) - - }; - } - - /** - * Uploads the given image, applies the correct title and license for the known user. - * Will then add this image to the OSM-feature or the OSM-note - */ - public async uploadImageAndApply(file: File, tagsStore: UIEventSource) : Promise{ - - const sizeInBytes = file.size; - const tags= tagsStore.data - const featureId = tags.id; - const self = this; - if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { - this.increaseCountFor(this._uploadStarted, featureId); - this.increaseCountFor(this._uploadFailed, featureId); - throw ( - Translations.t.image.toBig.Subs({ - actual_size: Math.floor(sizeInBytes / 1000000) + "MB", - max_size: self._uploader.maxFileSizeInMegabytes + "MB" - }).txt - ); + constructor( + layout: LayoutConfig, + uploader: ImageUploader, + featureProperties: FeaturePropertiesStore, + osmConnection: OsmConnection, + changes: Changes + ) { + this._uploader = uploader + this._featureProperties = featureProperties + this._layout = layout + this._osmConnection = osmConnection + this._changes = changes } - - const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); - const license = licenseStore?.data ?? "CC0"; - - const matchingLayer = this._layout?.getMatchingLayer(tags); - - const title = - matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ?? - tags.name ?? - "https//osm.org/" + tags.id; - const description = [ - "author:" + this._osmConnection.userDetails.data.name, - "license:" + license, - "osmid:" + tags.id - ].join("\n"); - - console.log("Upload done, creating "); - const action = await this.uploadImageWithLicense(featureId, title, description, file); - if(!isNaN(Number( featureId))){ - // THis is a map note - const url = action._url - await this._osmConnection.addCommentToNote(featureId, url) - NoteCommentElement.addCommentTo(url, > tagsStore, {osmConnection: this._osmConnection}) - return + /** + * Gets various counters. + * Note that counters can only increase + * If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased + * @param featureId: the id of the feature you want information for. '*' has a global counter + */ + public getCountsFor(featureId: string | "*"): { + retried: Store + uploadStarted: Store + retrySuccess: Store + failed: Store + uploadFinished: Store + } { + return { + uploadStarted: this.getCounterFor(this._uploadStarted, featureId), + uploadFinished: this.getCounterFor(this._uploadFinished, featureId), + retried: this.getCounterFor(this._uploadRetried, featureId), + failed: this.getCounterFor(this._uploadFailed, featureId), + retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId), + } } - await this._changes.applyAction(action); - } - private async uploadImageWithLicense( - featureId: OsmId, - title: string, description: string, blob: File - ): Promise { - this.increaseCountFor(this._uploadStarted, featureId); - const properties = this._featureProperties.getStore(featureId); - let key: string; - let value: string; - try { - ({ key, value } = await this._uploader.uploadImage(title, description, blob)); - } catch (e) { - this.increaseCountFor(this._uploadRetried, featureId); - console.error("Could not upload image, trying again:", e); - try { + /** + * Uploads the given image, applies the correct title and license for the known user. + * Will then add this image to the OSM-feature or the OSM-note + */ + public async uploadImageAndApply(file: File, tagsStore: UIEventSource): Promise { + const sizeInBytes = file.size + const tags = tagsStore.data + const featureId = tags.id + const self = this + if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { + this.increaseCountFor(this._uploadStarted, featureId) + this.increaseCountFor(this._uploadFailed, featureId) + throw Translations.t.image.toBig.Subs({ + actual_size: Math.floor(sizeInBytes / 1000000) + "MB", + max_size: self._uploader.maxFileSizeInMegabytes + "MB", + }).txt + } - ({ key, value } = await this._uploader.uploadImage(title, description, blob)); - this.increaseCountFor(this._uploadRetriedSuccess, featureId); - } catch (e) { - console.error("Could again not upload image due to", e); - this.increaseCountFor(this._uploadFailed, featureId); - } + const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0") + const license = licenseStore?.data ?? "CC0" + const matchingLayer = this._layout?.getMatchingLayer(tags) + + const title = + matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ?? + tags.name ?? + "https//osm.org/" + tags.id + const description = [ + "author:" + this._osmConnection.userDetails.data.name, + "license:" + license, + "osmid:" + tags.id, + ].join("\n") + + console.log("Upload done, creating ") + const action = await this.uploadImageWithLicense(featureId, title, description, file) + if (!isNaN(Number(featureId))) { + // THis is a map note + const url = action._url + await this._osmConnection.addCommentToNote(featureId, url) + NoteCommentElement.addCommentTo(url, >tagsStore, { + osmConnection: this._osmConnection, + }) + return + } + await this._changes.applyAction(action) } - console.log("Uploading done, creating action for", featureId); - const action = new LinkImageAction(featureId, key, value, properties, { - theme: this._layout.id, - changeType: "add-image" - }); - this.increaseCountFor(this._uploadFinished, featureId); - return action; - } - private getCounterFor(collection: Map>, key: string | "*") { - if (this._featureProperties.aliases.has(key)) { - key = this._featureProperties.aliases.get(key); + private async uploadImageWithLicense( + featureId: OsmId, + title: string, + description: string, + blob: File + ): Promise { + this.increaseCountFor(this._uploadStarted, featureId) + const properties = this._featureProperties.getStore(featureId) + let key: string + let value: string + try { + ;({ key, value } = await this._uploader.uploadImage(title, description, blob)) + } catch (e) { + this.increaseCountFor(this._uploadRetried, featureId) + console.error("Could not upload image, trying again:", e) + try { + ;({ key, value } = await this._uploader.uploadImage(title, description, blob)) + this.increaseCountFor(this._uploadRetriedSuccess, featureId) + } catch (e) { + console.error("Could again not upload image due to", e) + this.increaseCountFor(this._uploadFailed, featureId) + } + } + console.log("Uploading done, creating action for", featureId) + const action = new LinkImageAction(featureId, key, value, properties, { + theme: this._layout.id, + changeType: "add-image", + }) + this.increaseCountFor(this._uploadFinished, featureId) + return action } - if (!collection.has(key)) { - collection.set(key, new UIEventSource(0)); + + private getCounterFor(collection: Map>, key: string | "*") { + if (this._featureProperties.aliases.has(key)) { + key = this._featureProperties.aliases.get(key) + } + if (!collection.has(key)) { + collection.set(key, new UIEventSource(0)) + } + return collection.get(key) } - return collection.get(key); - } - - private increaseCountFor(collection: Map>, key: string | "*") { - const counter = this.getCounterFor(collection, key); - counter.setData(counter.data + 1); - const global = this.getCounterFor(collection, "*"); - global.setData(counter.data + 1); - } + private increaseCountFor(collection: Map>, key: string | "*") { + const counter = this.getCounterFor(collection, key) + counter.setData(counter.data + 1) + const global = this.getCounterFor(collection, "*") + global.setData(counter.data + 1) + } } diff --git a/src/Logic/ImageProviders/ImageUploader.ts b/src/Logic/ImageProviders/ImageUploader.ts index 3efb8d279..40601892b 100644 --- a/src/Logic/ImageProviders/ImageUploader.ts +++ b/src/Logic/ImageProviders/ImageUploader.ts @@ -1,5 +1,5 @@ export interface ImageUploader { - maxFileSizeInMegabytes?: number; + maxFileSizeInMegabytes?: number /** * Uploads the 'blob' as image, with some metadata. * Returns the URL to be linked + the appropriate key to add this to OSM @@ -11,5 +11,5 @@ export interface ImageUploader { title: string, description: string, blob: File - ): Promise<{ key: string, value: string }>; + ): Promise<{ key: string; value: string }> } diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index 4e4a1c541..536616403 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -1,15 +1,15 @@ -import ImageProvider, { ProvidedImage } from "./ImageProvider"; -import BaseUIElement from "../../UI/BaseUIElement"; -import { Utils } from "../../Utils"; -import Constants from "../../Models/Constants"; -import { LicenseInfo } from "./LicenseInfo"; -import { ImageUploader } from "./ImageUploader"; +import ImageProvider, { ProvidedImage } from "./ImageProvider" +import BaseUIElement from "../../UI/BaseUIElement" +import { Utils } from "../../Utils" +import Constants from "../../Models/Constants" +import { LicenseInfo } from "./LicenseInfo" +import { ImageUploader } from "./ImageUploader" -export class Imgur extends ImageProvider implements ImageUploader{ +export class Imgur extends ImageProvider implements ImageUploader { public static readonly defaultValuePrefix = ["https://i.imgur.com"] public static readonly singleton = new Imgur() public readonly defaultKeyPrefixes: string[] = ["image"] - public readonly maxFileSizeInMegabytes = 10 + public readonly maxFileSizeInMegabytes = 10 private constructor() { super() } @@ -24,7 +24,7 @@ export class Imgur extends ImageProvider implements ImageUploader{ title: string, description: string, blob: File - ): Promise<{ key: string, value: string }> { + ): Promise<{ key: string; value: string }> { const apiUrl = "https://api.imgur.com/3/image" const apiKey = Constants.ImgurApiKey @@ -33,7 +33,6 @@ export class Imgur extends ImageProvider implements ImageUploader{ formData.append("title", title) formData.append("description", description) - const settings: RequestInit = { method: "POST", body: formData, diff --git a/src/Logic/Osm/Actions/LinkImageAction.ts b/src/Logic/Osm/Actions/LinkImageAction.ts index 7d4ec23c8..908ce1583 100644 --- a/src/Logic/Osm/Actions/LinkImageAction.ts +++ b/src/Logic/Osm/Actions/LinkImageAction.ts @@ -1,15 +1,15 @@ -import ChangeTagAction from "./ChangeTagAction"; -import { Tag } from "../../Tags/Tag"; -import OsmChangeAction from "./OsmChangeAction"; -import { Changes } from "../Changes"; -import { ChangeDescription } from "./ChangeDescription"; -import { Store } from "../../UIEventSource"; +import ChangeTagAction from "./ChangeTagAction" +import { Tag } from "../../Tags/Tag" +import OsmChangeAction from "./OsmChangeAction" +import { Changes } from "../Changes" +import { ChangeDescription } from "./ChangeDescription" +import { Store } from "../../UIEventSource" export default class LinkImageAction extends OsmChangeAction { - private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; - public readonly _url: string; - private readonly _currentTags: Store>; - private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; + private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string + public readonly _url: string + private readonly _currentTags: Store> + private readonly _meta: { theme: string; changeType: "add-image" | "link-image" } /** * Adds an image-link to a feature @@ -31,10 +31,10 @@ export default class LinkImageAction extends OsmChangeAction { } ) { super(elementId, true) - this._proposedKey = proposedKey; - this._url = url; - this._currentTags = currentTags; - this._meta = meta; + this._proposedKey = proposedKey + this._url = url + this._currentTags = currentTags + this._meta = meta } protected CreateChangeDescriptions(): Promise { @@ -46,9 +46,12 @@ export default class LinkImageAction extends OsmChangeAction { key = this._proposedKey + ":" + i i++ } - const tagChangeAction = new ChangeTagAction ( this.mainObjectId, new Tag(key, url), currentTags, this._meta) + const tagChangeAction = new ChangeTagAction( + this.mainObjectId, + new Tag(key, url), + currentTags, + this._meta + ) return tagChangeAction.CreateChangeDescriptions() } - - } diff --git a/src/Logic/Osm/ChangesetHandler.ts b/src/Logic/Osm/ChangesetHandler.ts index 4b2a70b32..f3d78336d 100644 --- a/src/Logic/Osm/ChangesetHandler.ts +++ b/src/Logic/Osm/ChangesetHandler.ts @@ -5,7 +5,7 @@ import Locale from "../../UI/i18n/Locale" import Constants from "../../Models/Constants" import { Changes } from "./Changes" import { Utils } from "../../Utils" -import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" export interface ChangesetTag { key: string @@ -30,11 +30,14 @@ export class ChangesetHandler { constructor( dryRun: Store, osmConnection: OsmConnection, - allElements: FeaturePropertiesStore | { addAlias: (id0: string, id1: string) => void } | undefined, + allElements: + | FeaturePropertiesStore + | { addAlias: (id0: string, id1: string) => void } + | undefined, changes: Changes ) { this.osmConnection = osmConnection - this.allElements = allElements + this.allElements = allElements this.changes = changes this._dryRun = dryRun this.userDetails = osmConnection.userDetails diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 07028c35a..6f4e3755b 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -1,553 +1,553 @@ // @ts-ignore -import { osmAuth } from "osm-auth"; -import { Store, Stores, UIEventSource } from "../UIEventSource"; -import { OsmPreferences } from "./OsmPreferences"; -import { Utils } from "../../Utils"; -import { LocalStorageSource } from "../Web/LocalStorageSource"; -import * as config from "../../../package.json"; +import { osmAuth } from "osm-auth" +import { Store, Stores, UIEventSource } from "../UIEventSource" +import { OsmPreferences } from "./OsmPreferences" +import { Utils } from "../../Utils" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import * as config from "../../../package.json" export default class UserDetails { - public loggedIn = false; - public name = "Not logged in"; - public uid: number; - public csCount = 0; - public img?: string; - public unreadMessages = 0; - public totalMessages: number = 0; - public home: { lon: number; lat: number }; - public backend: string; - public account_created: string; - public tracesCount: number = 0; - public description: string; + public loggedIn = false + public name = "Not logged in" + public uid: number + public csCount = 0 + public img?: string + public unreadMessages = 0 + public totalMessages: number = 0 + public home: { lon: number; lat: number } + public backend: string + public account_created: string + public tracesCount: number = 0 + public description: string - constructor(backend: string) { - this.backend = backend; - } + constructor(backend: string) { + this.backend = backend + } } export interface AuthConfig { - "#"?: string; // optional comment - oauth_client_id: string; - oauth_secret: string; - url: string; + "#"?: string // optional comment + oauth_client_id: string + oauth_secret: string + url: string } export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export class OsmConnection { - public static readonly oauth_configs: Record = - config.config.oauth_credentials; - public auth; - public userDetails: UIEventSource; - public isLoggedIn: Store; - public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" - ); - public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" - ); + public static readonly oauth_configs: Record = + config.config.oauth_credentials + public auth + public userDetails: UIEventSource + public isLoggedIn: Store + public gpxServiceIsOnline: UIEventSource = new UIEventSource( + "unknown" + ) + public apiIsOnline: UIEventSource = new UIEventSource( + "unknown" + ) - public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" - ); - public preferencesHandler: OsmPreferences; - public readonly _oauth_config: AuthConfig; - private readonly _dryRun: Store; - private fakeUser: boolean; - private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; - private readonly _iframeMode: Boolean | boolean; - private readonly _singlePage: boolean; - private isChecking = false; + public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( + "not-attempted" + ) + public preferencesHandler: OsmPreferences + public readonly _oauth_config: AuthConfig + private readonly _dryRun: Store + private fakeUser: boolean + private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] + private readonly _iframeMode: Boolean | boolean + private readonly _singlePage: boolean + private isChecking = false - constructor(options?: { - dryRun?: Store - fakeUser?: false | boolean - oauth_token?: UIEventSource - // Used to keep multiple changesets open and to write to the correct changeset - singlePage?: boolean - osmConfiguration?: "osm" | "osm-test" - attemptLogin?: true | boolean - }) { - options = options ?? {}; - this.fakeUser = options.fakeUser ?? false; - this._singlePage = options.singlePage ?? true; - this._oauth_config = - OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? - OsmConnection.oauth_configs.osm; - console.debug("Using backend", this._oauth_config.url); - this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; + constructor(options?: { + dryRun?: Store + fakeUser?: false | boolean + oauth_token?: UIEventSource + // Used to keep multiple changesets open and to write to the correct changeset + singlePage?: boolean + osmConfiguration?: "osm" | "osm-test" + attemptLogin?: true | boolean + }) { + options = options ?? {} + this.fakeUser = options.fakeUser ?? false + this._singlePage = options.singlePage ?? true + this._oauth_config = + OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? + OsmConnection.oauth_configs.osm + console.debug("Using backend", this._oauth_config.url) + this._iframeMode = Utils.runningFromConsole ? false : window !== window.top - // Check if there are settings available in environment variables, and if so, use those - if ( - import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && - import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined - ) { - console.debug("Using environment variables for oauth config"); - this._oauth_config = { - oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, - oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, - url: "https://api.openstreetmap.org" - }; - } - - this.userDetails = new UIEventSource( - new UserDetails(this._oauth_config.url), - "userDetails" - ); - if (options.fakeUser) { - const ud = this.userDetails.data; - ud.csCount = 5678; - ud.loggedIn = true; - ud.unreadMessages = 0; - ud.name = "Fake user"; - ud.totalMessages = 42; - } - const self = this; - this.UpdateCapabilities(); - this.isLoggedIn = this.userDetails.map( - (user) => - user.loggedIn && - (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), - [this.apiIsOnline] - ); - this.isLoggedIn.addCallback((isLoggedIn) => { - if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { - // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do - // This means someone attempted to toggle this; so we attempt to login! - self.AttemptLogin(); - } - }); - - this._dryRun = options.dryRun ?? new UIEventSource(false); - - this.updateAuthObject(); - - this.preferencesHandler = new OsmPreferences( - this.auth, - this - ); - - if (options.oauth_token?.data !== undefined) { - console.log(options.oauth_token.data); - const self = this; - this.auth.bootstrapToken( - options.oauth_token.data, - (x) => { - console.log("Called back: ", x); - self.AttemptLogin(); - }, - this.auth - ); - - options.oauth_token.setData(undefined); - } - if (this.auth.authenticated() && options.attemptLogin !== false) { - this.AttemptLogin(); // Also updates the user badge - } else { - console.log("Not authenticated"); - } - } - - public GetPreference( - key: string, - defaultValue: string = undefined, - options?: { - documentation?: string - prefix?: string - } - ): UIEventSource { - return this.preferencesHandler.GetPreference(key, defaultValue, options); - } - - public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { - return this.preferencesHandler.GetLongPreference(key, prefix); - } - - public OnLoggedIn(action: (userDetails: UserDetails) => void) { - this._onLoggedIn.push(action); - } - - public LogOut() { - this.auth.logout(); - this.userDetails.data.loggedIn = false; - this.userDetails.data.csCount = 0; - this.userDetails.data.name = ""; - this.userDetails.ping(); - console.log("Logged out"); - this.loadingStatus.setData("not-attempted"); - } - - /** - * The backend host, without path or trailing '/' - * - * new OsmConnection().Backend() // => "https://www.openstreetmap.org" - */ - public Backend(): string { - return this._oauth_config.url; - } - - public AttemptLogin() { - this.UpdateCapabilities(); - this.loadingStatus.setData("loading"); - if (this.fakeUser) { - this.loadingStatus.setData("logged-in"); - console.log("AttemptLogin called, but ignored as fakeUser is set"); - return; - } - const self = this; - console.log("Trying to log in..."); - this.updateAuthObject(); - LocalStorageSource.Get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href - ); - this.auth.xhr( - { - method: "GET", - path: "/api/0.6/user/details" - }, - function(err, details) { - if (err != null) { - console.log(err); - self.loadingStatus.setData("error"); - if (err.status == 401) { - console.log("Clearing tokens..."); - // Not authorized - our token probably got revoked - self.auth.logout(); - self.LogOut(); - } - return; + // Check if there are settings available in environment variables, and if so, use those + if ( + import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && + import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined + ) { + console.debug("Using environment variables for oauth config") + this._oauth_config = { + oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, + oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, + url: "https://api.openstreetmap.org", + } } - if (details == null) { - self.loadingStatus.setData("error"); - return; + this.userDetails = new UIEventSource( + new UserDetails(this._oauth_config.url), + "userDetails" + ) + if (options.fakeUser) { + const ud = this.userDetails.data + ud.csCount = 5678 + ud.loggedIn = true + ud.unreadMessages = 0 + ud.name = "Fake user" + ud.totalMessages = 42 + } + const self = this + this.UpdateCapabilities() + this.isLoggedIn = this.userDetails.map( + (user) => + user.loggedIn && + (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), + [this.apiIsOnline] + ) + this.isLoggedIn.addCallback((isLoggedIn) => { + if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { + // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do + // This means someone attempted to toggle this; so we attempt to login! + self.AttemptLogin() + } + }) + + this._dryRun = options.dryRun ?? new UIEventSource(false) + + this.updateAuthObject() + + this.preferencesHandler = new OsmPreferences( + this.auth, + this + ) + + if (options.oauth_token?.data !== undefined) { + console.log(options.oauth_token.data) + const self = this + this.auth.bootstrapToken( + options.oauth_token.data, + (x) => { + console.log("Called back: ", x) + self.AttemptLogin() + }, + this.auth + ) + + options.oauth_token.setData(undefined) + } + if (this.auth.authenticated() && options.attemptLogin !== false) { + this.AttemptLogin() // Also updates the user badge + } else { + console.log("Not authenticated") + } + } + + public GetPreference( + key: string, + defaultValue: string = undefined, + options?: { + documentation?: string + prefix?: string + } + ): UIEventSource { + return this.preferencesHandler.GetPreference(key, defaultValue, options) + } + + public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { + return this.preferencesHandler.GetLongPreference(key, prefix) + } + + public OnLoggedIn(action: (userDetails: UserDetails) => void) { + this._onLoggedIn.push(action) + } + + public LogOut() { + this.auth.logout() + this.userDetails.data.loggedIn = false + this.userDetails.data.csCount = 0 + this.userDetails.data.name = "" + this.userDetails.ping() + console.log("Logged out") + this.loadingStatus.setData("not-attempted") + } + + /** + * The backend host, without path or trailing '/' + * + * new OsmConnection().Backend() // => "https://www.openstreetmap.org" + */ + public Backend(): string { + return this._oauth_config.url + } + + public AttemptLogin() { + this.UpdateCapabilities() + this.loadingStatus.setData("loading") + if (this.fakeUser) { + this.loadingStatus.setData("logged-in") + console.log("AttemptLogin called, but ignored as fakeUser is set") + return + } + const self = this + console.log("Trying to log in...") + this.updateAuthObject() + LocalStorageSource.Get("location_before_login").setData( + Utils.runningFromConsole ? undefined : window.location.href + ) + this.auth.xhr( + { + method: "GET", + path: "/api/0.6/user/details", + }, + function (err, details) { + if (err != null) { + console.log(err) + self.loadingStatus.setData("error") + if (err.status == 401) { + console.log("Clearing tokens...") + // Not authorized - our token probably got revoked + self.auth.logout() + self.LogOut() + } + return + } + + if (details == null) { + self.loadingStatus.setData("error") + return + } + + self.CheckForMessagesContinuously() + + // details is an XML DOM of user details + let userInfo = details.getElementsByTagName("user")[0] + + let data = self.userDetails.data + data.loggedIn = true + console.log("Login completed, userinfo is ", userInfo) + data.name = userInfo.getAttribute("display_name") + data.account_created = userInfo.getAttribute("account_created") + data.uid = Number(userInfo.getAttribute("id")) + data.csCount = Number.parseInt( + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + ) + data.tracesCount = Number.parseInt( + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 + ) + + data.img = undefined + const imgEl = userInfo.getElementsByTagName("img") + if (imgEl !== undefined && imgEl[0] !== undefined) { + data.img = imgEl[0].getAttribute("href") + } + + const description = userInfo.getElementsByTagName("description") + if (description !== undefined && description[0] !== undefined) { + data.description = description[0]?.innerHTML + } + const homeEl = userInfo.getElementsByTagName("home") + if (homeEl !== undefined && homeEl[0] !== undefined) { + const lat = parseFloat(homeEl[0].getAttribute("lat")) + const lon = parseFloat(homeEl[0].getAttribute("lon")) + data.home = { lat: lat, lon: lon } + } + + self.loadingStatus.setData("logged-in") + const messages = userInfo + .getElementsByTagName("messages")[0] + .getElementsByTagName("received")[0] + data.unreadMessages = parseInt(messages.getAttribute("unread")) + data.totalMessages = parseInt(messages.getAttribute("count")) + + self.userDetails.ping() + for (const action of self._onLoggedIn) { + action(self.userDetails.data) + } + self._onLoggedIn = [] + } + ) + } + + /** + * Interact with the API. + * + * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' + */ + public async interact( + path: string, + method: "GET" | "POST" | "PUT" | "DELETE", + header?: Record, + content?: string + ): Promise { + return new Promise((ok, error) => { + this.auth.xhr( + { + method, + options: { + header, + }, + content, + path: `/api/0.6/${path}`, + }, + function (err, response) { + if (err !== null) { + error(err) + } else { + ok(response) + } + } + ) + }) + } + + public async post( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "POST", header, content) + } + + public async put( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "PUT", header, content) + } + + public async get(path: string, header?: Record): Promise { + return await this.interact(path, "GET", header) + } + + public closeNote(id: number | string, text?: string): Promise { + let textSuffix = "" + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text) + } + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) + return new Promise((ok) => { + ok() + }) + } + return this.post(`notes/${id}/close${textSuffix}`) + } + + public reopenNote(id: number | string, text?: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) + return new Promise((ok) => { + ok() + }) + } + let textSuffix = "" + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text) + } + return this.post(`notes/${id}/reopen${textSuffix}`) + } + + public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually opening note with text ", text) + return new Promise<{ id: number }>((ok) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ) + }) + } + // Lat and lon must be strings for the API to accept it + const content = `lat=${lat}&lon=${lon}&text=${encodeURIComponent(text)}` + const response = await this.post("notes.json", content, { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + }) + const parsed = JSON.parse(response) + const id = parsed.properties + console.log("OPENED NOTE", id) + return id + } + + public async uploadGpxTrack( + gpx: string, + options: { + description: string + visibility: "private" | "public" | "trackable" | "identifiable" + filename?: string + /** + * Some words to give some properties; + * + * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. + */ + labels: string[] + } + ): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually uploading GPX ", gpx) + return new Promise<{ id: number }>((ok, error) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ) + }) } - self.CheckForMessagesContinuously(); - - // details is an XML DOM of user details - let userInfo = details.getElementsByTagName("user")[0]; - - let data = self.userDetails.data; - data.loggedIn = true; - console.log("Login completed, userinfo is ", userInfo); - data.name = userInfo.getAttribute("display_name"); - data.account_created = userInfo.getAttribute("account_created"); - data.uid = Number(userInfo.getAttribute("id")); - data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 - ); - data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 - ); - - data.img = undefined; - const imgEl = userInfo.getElementsByTagName("img"); - if (imgEl !== undefined && imgEl[0] !== undefined) { - data.img = imgEl[0].getAttribute("href"); + const contents = { + file: gpx, + description: options.description ?? "", + tags: options.labels?.join(",") ?? "", + visibility: options.visibility, } - const description = userInfo.getElementsByTagName("description"); - if (description !== undefined && description[0] !== undefined) { - data.description = description[0]?.innerHTML; - } - const homeEl = userInfo.getElementsByTagName("home"); - if (homeEl !== undefined && homeEl[0] !== undefined) { - const lat = parseFloat(homeEl[0].getAttribute("lat")); - const lon = parseFloat(homeEl[0].getAttribute("lon")); - data.home = { lat: lat, lon: lon }; + const extras = { + file: + '; filename="' + + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + + '"\r\nContent-Type: application/gpx+xml', } - self.loadingStatus.setData("logged-in"); - const messages = userInfo - .getElementsByTagName("messages")[0] - .getElementsByTagName("received")[0]; - data.unreadMessages = parseInt(messages.getAttribute("unread")); - data.totalMessages = parseInt(messages.getAttribute("count")); + const boundary = "987654" - self.userDetails.ping(); - for (const action of self._onLoggedIn) { - action(self.userDetails.data); + let body = "" + for (const key in contents) { + body += "--" + boundary + "\r\n" + body += 'Content-Disposition: form-data; name="' + key + '"' + if (extras[key] !== undefined) { + body += extras[key] + } + body += "\r\n\r\n" + body += contents[key] + "\r\n" } - self._onLoggedIn = []; - } - ); - } + body += "--" + boundary + "--\r\n" - /** - * Interact with the API. - * - * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' - */ - public async interact( - path: string, - method: "GET" | "POST" | "PUT" | "DELETE", - header?: Record, - content?: string - ): Promise { - return new Promise((ok, error) => { - this.auth.xhr( - { - method, - options: { - header - }, - content, - path: `/api/0.6/${path}` - }, - function(err, response) { - if (err !== null) { - error(err); - } else { - ok(response); - } + const response = await this.post("gpx/create", body, { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length, + }) + const parsed = JSON.parse(response) + console.log("Uploaded GPX track", parsed) + return { id: parsed } + } + + public addCommentToNote(id: number | string, text: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id) + return new Promise((ok) => { + ok() + }) } - ); - }); - } - - public async post( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "POST", header, content); - } - - public async put( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "PUT", header, content); - } - - public async get(path: string, header?: Record): Promise { - return await this.interact(path, "GET", header); - } - - public closeNote(id: number | string, text?: string): Promise { - let textSuffix = ""; - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text); - } - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text); - return new Promise((ok) => { - ok(); - }); - } - return this.post(`notes/${id}/close${textSuffix}`); - } - - public reopenNote(id: number | string, text?: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text); - return new Promise((ok) => { - ok(); - }); - } - let textSuffix = ""; - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text); - } - return this.post(`notes/${id}/reopen${textSuffix}`); - } - - public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually opening note with text ", text); - return new Promise<{ id: number }>((ok) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ); - }); - } - // Lat and lon must be strings for the API to accept it - const content = `lat=${lat}&lon=${lon}&text=${encodeURIComponent(text)}` - const response = await this.post("notes.json", content, { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" - }); - const parsed = JSON.parse(response); - const id = parsed.properties; - console.log("OPENED NOTE", id); - return id; - } - - public async uploadGpxTrack( - gpx: string, - options: { - description: string - visibility: "private" | "public" | "trackable" | "identifiable" - filename?: string - /** - * Some words to give some properties; - * - * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. - */ - labels: string[] - } - ): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually uploading GPX ", gpx); - return new Promise<{ id: number }>((ok, error) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ); - }); - } - - const contents = { - file: gpx, - description: options.description ?? "", - tags: options.labels?.join(",") ?? "", - visibility: options.visibility - }; - - const extras = { - file: - "; filename=\"" + - (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - "\"\r\nContent-Type: application/gpx+xml" - }; - - const boundary = "987654"; - - let body = ""; - for (const key in contents) { - body += "--" + boundary + "\r\n"; - body += "Content-Disposition: form-data; name=\"" + key + "\""; - if (extras[key] !== undefined) { - body += extras[key]; - } - body += "\r\n\r\n"; - body += contents[key] + "\r\n"; - } - body += "--" + boundary + "--\r\n"; - - const response = await this.post("gpx/create", body, { - "Content-Type": "multipart/form-data; boundary=" + boundary, - "Content-Length": body.length - }); - const parsed = JSON.parse(response); - console.log("Uploaded GPX track", parsed); - return { id: parsed }; - } - - public addCommentToNote(id: number | string, text: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id); - return new Promise((ok) => { - ok(); - }); - } - if ((text ?? "") === "") { - throw "Invalid text!"; - } - - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", - - path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` - }, - function(err, _) { - if (err !== null) { - error(err); - } else { - ok(); - } + if ((text ?? "") === "") { + throw "Invalid text!" } - ); - }); - } - /** - * To be called by land.html - */ - public finishLogin(callback: (previousURL: string) => void) { - this.auth.authenticate(function() { - // Fully authed at this point - console.log("Authentication successful!"); - const previousLocation = LocalStorageSource.Get("location_before_login"); - callback(previousLocation.data); - }); - } + return new Promise((ok, error) => { + this.auth.xhr( + { + method: "POST", - private updateAuthObject() { - let pwaStandAloneMode = false; - try { - if (Utils.runningFromConsole) { - pwaStandAloneMode = true; - } else if ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: fullscreen)").matches - ) { - pwaStandAloneMode = true; - } - } catch (e) { - console.warn( - "Detecting standalone mode failed", - e, - ". Assuming in browser and not worrying furhter" - ); + path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, + }, + function (err, _) { + if (err !== null) { + error(err) + } else { + ok() + } + } + ) + }) } - const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage; - // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... - // Same for an iframe... - - this.auth = new osmAuth({ - client_id: this._oauth_config.oauth_client_id, - url: this._oauth_config.url, - scope: "read_prefs write_prefs write_api write_gpx write_notes", - redirect_uri: Utils.runningFromConsole - ? "https://mapcomplete.org/land.html" - : window.location.protocol + "//" + window.location.host + "/land.html", - singlepage: !standalone, - auto: true - }); - } - - private CheckForMessagesContinuously() { - const self = this; - if (this.isChecking) { - return; + /** + * To be called by land.html + */ + public finishLogin(callback: (previousURL: string) => void) { + this.auth.authenticate(function () { + // Fully authed at this point + console.log("Authentication successful!") + const previousLocation = LocalStorageSource.Get("location_before_login") + callback(previousLocation.data) + }) } - this.isChecking = true; - Stores.Chronic(5 * 60 * 1000).addCallback((_) => { - if (self.isLoggedIn.data) { - console.log("Checking for messages"); - self.AttemptLogin(); - } - }); - } - private UpdateCapabilities(): void { - const self = this; - this.FetchCapabilities().then(({ api, gpx }) => { - self.apiIsOnline.setData(api); - self.gpxServiceIsOnline.setData(gpx); - }); - } + private updateAuthObject() { + let pwaStandAloneMode = false + try { + if (Utils.runningFromConsole) { + pwaStandAloneMode = true + } else if ( + window.matchMedia("(display-mode: standalone)").matches || + window.matchMedia("(display-mode: fullscreen)").matches + ) { + pwaStandAloneMode = true + } + } catch (e) { + console.warn( + "Detecting standalone mode failed", + e, + ". Assuming in browser and not worrying furhter" + ) + } + const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage - private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { - if (Utils.runningFromConsole) { - return { api: "online", gpx: "online" }; + // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... + // Same for an iframe... + + this.auth = new osmAuth({ + client_id: this._oauth_config.oauth_client_id, + url: this._oauth_config.url, + scope: "read_prefs write_prefs write_api write_gpx write_notes", + redirect_uri: Utils.runningFromConsole + ? "https://mapcomplete.org/land.html" + : window.location.protocol + "//" + window.location.host + "/land.html", + singlepage: !standalone, + auto: true, + }) } - const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities"); - if (result["content"] === undefined) { - console.log("Something went wrong:", result); - return { api: "unreachable", gpx: "unreachable" }; + + private CheckForMessagesContinuously() { + const self = this + if (this.isChecking) { + return + } + this.isChecking = true + Stores.Chronic(5 * 60 * 1000).addCallback((_) => { + if (self.isLoggedIn.data) { + console.log("Checking for messages") + self.AttemptLogin() + } + }) + } + + private UpdateCapabilities(): void { + const self = this + this.FetchCapabilities().then(({ api, gpx }) => { + self.apiIsOnline.setData(api) + self.gpxServiceIsOnline.setData(gpx) + }) + } + + private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { + if (Utils.runningFromConsole) { + return { api: "online", gpx: "online" } + } + const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") + if (result["content"] === undefined) { + console.log("Something went wrong:", result) + return { api: "unreachable", gpx: "unreachable" } + } + const xmlRaw = result["content"] + const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") + const statusEl = parsed.getElementsByTagName("status")[0] + const api = statusEl.getAttribute("api") + const gpx = statusEl.getAttribute("gpx") + return { api, gpx } } - const xmlRaw = result["content"]; - const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml"); - const statusEl = parsed.getElementsByTagName("status")[0]; - const api = statusEl.getAttribute("api"); - const gpx = statusEl.getAttribute("gpx"); - return { api, gpx }; - } } diff --git a/src/Logic/State/GeoLocationState.ts b/src/Logic/State/GeoLocationState.ts index fe395fde0..7c6c7b1e7 100644 --- a/src/Logic/State/GeoLocationState.ts +++ b/src/Logic/State/GeoLocationState.ts @@ -1,13 +1,13 @@ -import { UIEventSource } from "../UIEventSource"; -import { LocalStorageSource } from "../Web/LocalStorageSource"; -import { QueryParameters } from "../Web/QueryParameters"; +import { UIEventSource } from "../UIEventSource" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import { QueryParameters } from "../Web/QueryParameters" export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" export interface GeoLocationPointProperties extends GeolocationCoordinates { - id: "gps"; - "user:location": "yes"; - date: string; + id: "gps" + "user:location": "yes" + date: string } /** @@ -23,22 +23,22 @@ export class GeoLocationState { */ public readonly permission: UIEventSource = new UIEventSource( "prompt" - ); + ) /** * Important to determine e.g. if we move automatically on fix or not */ - public readonly requestMoment: UIEventSource = new UIEventSource(undefined); + public readonly requestMoment: UIEventSource = new UIEventSource(undefined) /** * If true: the map will center (and re-center) to this location */ - public readonly allowMoving: UIEventSource = new UIEventSource(true); + public readonly allowMoving: UIEventSource = new UIEventSource(true) /** * The latest GeoLocationCoordinates, as given by the WebAPI */ public readonly currentGPSLocation: UIEventSource = - new UIEventSource(undefined); + new UIEventSource(undefined) /** * A small flag on localstorage. If the user previously granted the geolocation, it will be set. @@ -50,46 +50,46 @@ export class GeoLocationState { */ private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = ( LocalStorageSource.Get("geolocation-permissions") - ); + ) /** * Used to detect a permission retraction */ - private readonly _grantedThisSession: UIEventSource = new UIEventSource(false); + private readonly _grantedThisSession: UIEventSource = new UIEventSource(false) constructor() { - const self = this; + const self = this this.permission.addCallbackAndRunD(async (state) => { if (state === "granted") { - self._previousLocationGrant.setData("true"); - self._grantedThisSession.setData(true); + self._previousLocationGrant.setData("true") + self._grantedThisSession.setData(true) } if (state === "prompt" && self._grantedThisSession.data) { // This is _really_ weird: we had a grant earlier, but it's 'prompt' now? // This means that the rights have been revoked again! - self._previousLocationGrant.setData("false"); - self.permission.setData("denied"); - self.currentGPSLocation.setData(undefined); - console.warn("Detected a downgrade in permissions!"); + self._previousLocationGrant.setData("false") + self.permission.setData("denied") + self.currentGPSLocation.setData(undefined) + console.warn("Detected a downgrade in permissions!") } if (state === "denied") { - self._previousLocationGrant.setData("false"); + self._previousLocationGrant.setData("false") } - }); - console.log("Previous location grant:", this._previousLocationGrant.data); + }) + console.log("Previous location grant:", this._previousLocationGrant.data) if (this._previousLocationGrant.data === "true") { // A previous visit successfully granted permission. Chance is high that we are allowed to use it again! // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them - this._previousLocationGrant.setData("false"); - console.log("Requesting access to GPS as this was previously granted"); + this._previousLocationGrant.setData("false") + console.log("Requesting access to GPS as this was previously granted") const latLonGivenViaUrl = - QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon"); + QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") if (!latLonGivenViaUrl) { - this.requestMoment.setData(new Date()); + this.requestMoment.setData(new Date()) } - this.requestPermission(); + this.requestPermission() } } @@ -101,37 +101,36 @@ export class GeoLocationState { public requestPermission() { if (typeof navigator === "undefined") { // Not compatible with this browser - this.permission.setData("denied"); - return; + this.permission.setData("denied") + return } if (this.permission.data !== "prompt" && this.permission.data !== "requested") { // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well // Hence that we continue the flow if it is "requested" - return; + return } - this.permission.setData("requested"); + this.permission.setData("requested") try { navigator?.permissions ?.query({ name: "geolocation" }) .then((status) => { - const self = this; - if(status.state === "granted" || status.state === "denied"){ + const self = this + if (status.state === "granted" || status.state === "denied") { self.permission.setData(status.state) return } status.addEventListener("change", (e) => { - self.permission.setData(status.state); - - }); + self.permission.setData(status.state) + }) // The code above might have reset it to 'prompt', but we _did_ request permission! this.permission.setData("requested") // We _must_ call 'startWatching', as that is the actual trigger for the popup... - self.startWatching(); + self.startWatching() }) - .catch((e) => console.error("Could not get geopermission", e)); + .catch((e) => console.error("Could not get geopermission", e)) } catch (e) { - console.error("Could not get permission:", e); + console.error("Could not get permission:", e) } } @@ -140,18 +139,18 @@ export class GeoLocationState { * @private */ private async startWatching() { - const self = this; + const self = this navigator.geolocation.watchPosition( - function(position) { - self.currentGPSLocation.setData(position.coords); - self._previousLocationGrant.setData("true"); + function (position) { + self.currentGPSLocation.setData(position.coords) + self._previousLocationGrant.setData("true") }, - function() { - console.warn("Could not get location with navigator.geolocation"); + function () { + console.warn("Could not get location with navigator.geolocation") }, { - enableHighAccuracy: true + enableHighAccuracy: true, } - ); + ) } } diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index 867ef45d5..21648e0b7 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -1,23 +1,23 @@ -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; -import { OsmConnection } from "../Osm/OsmConnection"; -import { MangroveIdentity } from "../Web/MangroveReviews"; -import { Store, Stores, UIEventSource } from "../UIEventSource"; -import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; -import { FeatureSource } from "../FeatureSource/FeatureSource"; -import { Feature } from "geojson"; -import { Utils } from "../../Utils"; -import translators from "../../assets/translators.json"; -import codeContributors from "../../assets/contributors.json"; -import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; -import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; -import usersettings from "../../../src/assets/generated/layers/usersettings.json"; -import Locale from "../../UI/i18n/Locale"; -import LinkToWeblate from "../../UI/Base/LinkToWeblate"; -import FeatureSwitchState from "./FeatureSwitchState"; -import Constants from "../../Models/Constants"; -import { QueryParameters } from "../Web/QueryParameters"; -import { ThemeMetaTagging } from "./UserSettingsMetaTagging"; -import { MapProperties } from "../../Models/MapProperties"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import { OsmConnection } from "../Osm/OsmConnection" +import { MangroveIdentity } from "../Web/MangroveReviews" +import { Store, Stores, UIEventSource } from "../UIEventSource" +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" +import { FeatureSource } from "../FeatureSource/FeatureSource" +import { Feature } from "geojson" +import { Utils } from "../../Utils" +import translators from "../../assets/translators.json" +import codeContributors from "../../assets/contributors.json" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" +import usersettings from "../../../src/assets/generated/layers/usersettings.json" +import Locale from "../../UI/i18n/Locale" +import LinkToWeblate from "../../UI/Base/LinkToWeblate" +import FeatureSwitchState from "./FeatureSwitchState" +import Constants from "../../Models/Constants" +import { QueryParameters } from "../Web/QueryParameters" +import { ThemeMetaTagging } from "./UserSettingsMetaTagging" +import { MapProperties } from "../../Models/MapProperties" /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -42,8 +42,10 @@ export default class UserRelatedState { public readonly fixateNorth: UIEventSource public readonly homeLocation: FeatureSource public readonly language: UIEventSource - public readonly preferredBackgroundLayer: UIEventSource - public readonly imageLicense : UIEventSource + public readonly preferredBackgroundLayer: UIEventSource< + string | "photo" | "map" | "osmbasedmap" | undefined + > + public readonly imageLicense: UIEventSource /** * The number of seconds that the GPS-locations are stored in memory. * Time in seconds @@ -61,7 +63,7 @@ export default class UserRelatedState { * Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource */ public readonly preferencesAsTags: UIEventSource> - private readonly _mapProperties: MapProperties; + private readonly _mapProperties: MapProperties constructor( osmConnection: OsmConnection, @@ -71,7 +73,7 @@ export default class UserRelatedState { mapProperties?: MapProperties ) { this.osmConnection = osmConnection - this._mapProperties = mapProperties; + this._mapProperties = mapProperties { const translationMode: UIEventSource = this.osmConnection.GetPreference("translation-mode", "false") @@ -104,12 +106,17 @@ export default class UserRelatedState { this.mangroveIdentity = new MangroveIdentity( this.osmConnection.GetLongPreference("identity", "mangrove") ) - this.preferredBackgroundLayer= this.osmConnection.GetPreference("preferred-background-layer", undefined, { - documentation: "The ID of a layer or layer category that MapComplete uses by default" - }) + this.preferredBackgroundLayer = this.osmConnection.GetPreference( + "preferred-background-layer", + undefined, + { + documentation: + "The ID of a layer or layer category that MapComplete uses by default", + } + ) - this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { - documentation: "The license under which new images are uploaded" + this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { + documentation: "The license under which new images are uploaded", }) this.installedUserThemes = this.InitInstalledUserThemes() @@ -277,7 +284,6 @@ export default class UserRelatedState { amendedPrefs.data["__url_parameter_initialized:" + key] = "yes" } - const osmConnection = this.osmConnection osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { for (const k in newPrefs) { @@ -405,13 +411,11 @@ export default class UserRelatedState { } } - - this._mapProperties?.rasterLayer?.addCallbackAndRun(l => { + this._mapProperties?.rasterLayer?.addCallbackAndRun((l) => { amendedPrefs.data["__current_background"] = l?.properties?.id amendedPrefs.ping() }) - return amendedPrefs } } diff --git a/src/Logic/State/UserSettingsMetaTagging.ts b/src/Logic/State/UserSettingsMetaTagging.ts index 33a5ae85b..6e568c5c3 100644 --- a/src/Logic/State/UserSettingsMetaTagging.ts +++ b/src/Logic/State/UserSettingsMetaTagging.ts @@ -1,14 +1,42 @@ import { Utils } from "../../Utils" /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ export class ThemeMetaTagging { - public static readonly themeName = "usersettings" + public static readonly themeName = "usersettings" - public metaTaggging_for_usersettings(feat: {properties: Record}) { - Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) - Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' ) - Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) - Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) - Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) - feat.properties['__current_backgroun'] = 'initial_value' - } -} \ No newline at end of file + public metaTaggging_for_usersettings(feat: { properties: Record }) { + Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => + feat.properties._description + .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) + ?.at(1) + ) + Utils.AddLazyProperty( + feat.properties, + "_d", + () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" + ) + Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => + ((feat) => { + const e = document.createElement("div") + e.innerHTML = feat.properties._d + return Array.from(e.getElementsByTagName("a")).filter( + (a) => a.href.match(/mastodon|en.osm.town/) !== null + )[0]?.href + })(feat) + ) + Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => + ((feat) => { + const e = document.createElement("div") + e.innerHTML = feat.properties._d + return Array.from(e.getElementsByTagName("a")).filter( + (a) => a.getAttribute("rel")?.indexOf("me") >= 0 + )[0]?.href + })(feat) + ) + Utils.AddLazyProperty( + feat.properties, + "_mastodon_candidate", + () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a + ) + feat.properties["__current_backgroun"] = "initial_value" + } +} diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index 15819a735..19e2228d4 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -1,35 +1,34 @@ -import { ImmutableStore, Store, UIEventSource } from "../UIEventSource"; -import { MangroveReviews, Review } from "mangrove-reviews-typescript"; -import { Utils } from "../../Utils"; -import { Feature, Position } from "geojson"; -import { GeoOperations } from "../GeoOperations"; +import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" +import { MangroveReviews, Review } from "mangrove-reviews-typescript" +import { Utils } from "../../Utils" +import { Feature, Position } from "geojson" +import { GeoOperations } from "../GeoOperations" export class MangroveIdentity { - public readonly keypair: Store; - public readonly key_id: Store; + public readonly keypair: Store + public readonly key_id: Store constructor(mangroveIdentity: UIEventSource) { - const key_id = new UIEventSource(undefined); - this.key_id = key_id; - const keypairEventSource = new UIEventSource(undefined); - this.keypair = keypairEventSource; + const key_id = new UIEventSource(undefined) + this.key_id = key_id + const keypairEventSource = new UIEventSource(undefined) + this.keypair = keypairEventSource mangroveIdentity.addCallbackAndRunD(async (data) => { if (data === "") { - return; + return } - const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)); - keypairEventSource.setData(keypair); - const pem = await MangroveReviews.publicToPem(keypair.publicKey); - key_id.setData(pem); - }); + const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)) + keypairEventSource.setData(keypair) + const pem = await MangroveReviews.publicToPem(keypair.publicKey) + key_id.setData(pem) + }) try { if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") { - MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => { - }); + MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {}) } } catch (e) { - console.error("Could not create identity: ", e); + console.error("Could not create identity: ", e) } } @@ -39,13 +38,13 @@ export class MangroveIdentity { * @constructor */ private static async CreateIdentity(identity: UIEventSource): Promise { - const keypair = await MangroveReviews.generateKeypair(); - const jwk = await MangroveReviews.keypairToJwk(keypair); + const keypair = await MangroveReviews.generateKeypair() + const jwk = await MangroveReviews.keypairToJwk(keypair) if ((identity.data ?? "") !== "") { // Identity has been loaded via osmPreferences by now - we don't overwrite - return; + return } - identity.setData(JSON.stringify(jwk)); + identity.setData(JSON.stringify(jwk)) } } @@ -53,18 +52,18 @@ export class MangroveIdentity { * Tracks all reviews of a given feature, allows to create a new review */ export default class FeatureReviews { - private static readonly _featureReviewsCache: Record = {}; - public readonly subjectUri: Store; - public readonly average: Store; + private static readonly _featureReviewsCache: Record = {} + public readonly subjectUri: Store + public readonly average: Store private readonly _reviews: UIEventSource<(Review & { madeByLoggedInUser: Store })[]> = - new UIEventSource([]); + new UIEventSource([]) public readonly reviews: Store<(Review & { madeByLoggedInUser: Store })[]> = - this._reviews; - private readonly _lat: number; - private readonly _lon: number; - private readonly _uncertainty: number; - private readonly _name: Store; - private readonly _identity: MangroveIdentity; + this._reviews + private readonly _lat: number + private readonly _lon: number + private readonly _uncertainty: number + private readonly _name: Store + private readonly _identity: MangroveIdentity private constructor( feature: Feature, @@ -77,72 +76,72 @@ export default class FeatureReviews { } ) { const centerLonLat = GeoOperations.centerpointCoordinates(feature) - ;[this._lon, this._lat] = centerLonLat; + ;[this._lon, this._lat] = centerLonLat this._identity = - mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)); - const nameKey = options?.nameKey ?? "name"; + mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)) + const nameKey = options?.nameKey ?? "name" if (feature.geometry.type === "Point") { - this._uncertainty = options?.uncertaintyRadius ?? 10; + this._uncertainty = options?.uncertaintyRadius ?? 10 } else { - let coordss: Position[][]; + let coordss: Position[][] if (feature.geometry.type === "LineString") { - coordss = [feature.geometry.coordinates]; + coordss = [feature.geometry.coordinates] } else if ( feature.geometry.type === "MultiLineString" || feature.geometry.type === "Polygon" ) { - coordss = feature.geometry.coordinates; + coordss = feature.geometry.coordinates } - let maxDistance = 0; + let maxDistance = 0 for (const coords of coordss) { for (const coord of coords) { maxDistance = Math.max( maxDistance, GeoOperations.distanceBetween(centerLonLat, coord) - ); + ) } } - this._uncertainty = options?.uncertaintyRadius ?? maxDistance; + this._uncertainty = options?.uncertaintyRadius ?? maxDistance } - this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName); + this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName) - this.subjectUri = this.ConstructSubjectUri(); + this.subjectUri = this.ConstructSubjectUri() - const self = this; + const self = this this.subjectUri.addCallbackAndRunD(async (sub) => { - const reviews = await MangroveReviews.getReviews({ sub }); - self.addReviews(reviews.reviews); - }); + const reviews = await MangroveReviews.getReviews({ sub }) + self.addReviews(reviews.reviews) + }) /* We also construct all subject queries _without_ encoding the name to work around a previous bug * See https://github.com/giggls/opencampsitemap/issues/30 */ this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => { try { - const reviews = await MangroveReviews.getReviews({ sub }); - self.addReviews(reviews.reviews); + const reviews = await MangroveReviews.getReviews({ sub }) + self.addReviews(reviews.reviews) } catch (e) { - console.log("Could not fetch reviews for partially incorrect query ", sub); + console.log("Could not fetch reviews for partially incorrect query ", sub) } - }); - this.average = this._reviews.map(reviews => { + }) + this.average = this._reviews.map((reviews) => { if (!reviews) { - return null; - } - if(reviews.length === 0){ return null } - let sum = 0; - let count = 0; + if (reviews.length === 0) { + return null + } + let sum = 0 + let count = 0 for (const review of reviews) { if (review.rating !== undefined) { - count++; - sum += review.rating; + count++ + sum += review.rating } } return Math.round(sum / count) - }); + }) } /** @@ -158,14 +157,14 @@ export default class FeatureReviews { uncertaintyRadius?: number } ) { - const key = feature.properties.id; - const cached = FeatureReviews._featureReviewsCache[key]; + const key = feature.properties.id + const cached = FeatureReviews._featureReviewsCache[key] if (cached !== undefined) { - return cached; + return cached } - const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options); - FeatureReviews._featureReviewsCache[key] = featureReviews; - return featureReviews; + const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options) + FeatureReviews._featureReviewsCache[key] = featureReviews + return featureReviews } /** @@ -174,15 +173,15 @@ export default class FeatureReviews { public async createReview(review: Omit): Promise { const r: Review = { sub: this.subjectUri.data, - ...review - }; - const keypair: CryptoKeyPair = this._identity.keypair.data; - console.log(r); - const jwt = await MangroveReviews.signReview(keypair, r); - console.log("Signed:", jwt); - await MangroveReviews.submitReview(jwt); - this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) }); - this._reviews.ping(); + ...review, + } + const keypair: CryptoKeyPair = this._identity.keypair.data + console.log(r) + const jwt = await MangroveReviews.signReview(keypair, r) + console.log("Signed:", jwt) + await MangroveReviews.submitReview(jwt) + this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) }) + this._reviews.ping() } /** @@ -191,48 +190,48 @@ export default class FeatureReviews { * @private */ private addReviews(reviews: { payload: Review; kid: string }[]) { - const self = this; - const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion)); + const self = this + const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion)) - let hasNew = false; + let hasNew = false for (const reviewData of reviews) { - const review = reviewData.payload; + const review = reviewData.payload try { - const url = new URL(review.sub); - console.log("URL is", url); + const url = new URL(review.sub) + console.log("URL is", url) if (url.protocol === "geo:") { const coordinate = <[number, number]>( url.pathname.split(",").map((n) => Number(n)) - ); + ) const distance = GeoOperations.distanceBetween( [this._lat, this._lon], coordinate - ); + ) if (distance > this._uncertainty) { - continue; + continue } } } catch (e) { - console.warn(e); + console.warn(e) } - const key = review.rating + " " + review.opinion; + const key = review.rating + " " + review.opinion if (alreadyKnown.has(key)) { - continue; + continue } self._reviews.data.push({ ...review, madeByLoggedInUser: this._identity.key_id.map((user_key_id) => { - return reviewData.kid === user_key_id; - }) - }); - hasNew = true; + return reviewData.kid === user_key_id + }), + }) + hasNew = true } if (hasNew) { self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first - self._reviews.ping(); + self._reviews.ping() } } @@ -245,13 +244,13 @@ export default class FeatureReviews { private ConstructSubjectUri(dontEncodeName: boolean = false): Store { // https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2 // `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3 - const self = this; - return this._name.map(function(name) { - let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`; + const self = this + return this._name.map(function (name) { + let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}` if (name) { - uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)); + uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)) } - return uri; - }); + return uri + }) } } diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts index dbd34894b..700a0cb30 100644 --- a/src/Models/RasterLayers.ts +++ b/src/Models/RasterLayers.ts @@ -1,41 +1,41 @@ -import { Feature, Polygon } from "geojson"; -import * as editorlayerindex from "../assets/editor-layer-index.json"; -import * as globallayers from "../assets/global-raster-layers.json"; -import { BBox } from "../Logic/BBox"; -import { Store, Stores } from "../Logic/UIEventSource"; -import { GeoOperations } from "../Logic/GeoOperations"; -import { RasterLayerProperties } from "./RasterLayerProperties"; +import { Feature, Polygon } from "geojson" +import * as editorlayerindex from "../assets/editor-layer-index.json" +import * as globallayers from "../assets/global-raster-layers.json" +import { BBox } from "../Logic/BBox" +import { Store, Stores } from "../Logic/UIEventSource" +import { GeoOperations } from "../Logic/GeoOperations" +import { RasterLayerProperties } from "./RasterLayerProperties" export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & - RasterLayerPolygon)[] = editorlayerindex.features; + RasterLayerPolygon)[] = editorlayerindex.features public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( (properties) => { type: "Feature", properties, - geometry: BBox.global.asGeometry() + geometry: BBox.global.asGeometry(), } - ); + ) public static readonly osmCartoProperties: RasterLayerProperties = { id: "osm", name: "OpenStreetMap", url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: { text: "OpenStreetMap", - url: "https://openStreetMap.org/copyright" + url: "https://openStreetMap.org/copyright", }, best: true, max_zoom: 19, min_zoom: 0, - category: "osmbasedmap" - }; + category: "osmbasedmap", + } public static readonly osmCarto: RasterLayerPolygon = { type: "Feature", properties: AvailableRasterLayers.osmCartoProperties, - geometry: BBox.global.asGeometry() - }; + geometry: BBox.global.asGeometry(), + } public static readonly maptilerDefaultLayer: RasterLayerPolygon = { type: "Feature", @@ -47,11 +47,11 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } + url: "https://www.maptiler.com/copyright/", + }, }, - geometry: BBox.global.asGeometry() - }; + geometry: BBox.global.asGeometry(), + } public static readonly maptilerCarto: RasterLayerPolygon = { type: "Feature", @@ -63,11 +63,11 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } + url: "https://www.maptiler.com/copyright/", + }, }, - geometry: BBox.global.asGeometry() - }; + geometry: BBox.global.asGeometry(), + } public static readonly maptilerBackdrop: RasterLayerPolygon = { type: "Feature", @@ -79,11 +79,11 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } + url: "https://www.maptiler.com/copyright/", + }, }, - geometry: BBox.global.asGeometry() - }; + geometry: BBox.global.asGeometry(), + } public static readonly americana: RasterLayerPolygon = { type: "Feature", properties: { @@ -94,43 +94,45 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Americana", - url: "https://github.com/ZeLonewolf/openstreetmap-americana/" - } + url: "https://github.com/ZeLonewolf/openstreetmap-americana/", + }, }, - geometry: BBox.global.asGeometry() - }; + geometry: BBox.global.asGeometry(), + } public static layersAvailableAt( location: Store<{ lon: number; lat: number }> ): Store { const availableLayersBboxes = Stores.ListStabilized( location.mapD((loc) => { - const lonlat: [number, number] = [loc.lon, loc.lat]; + const lonlat: [number, number] = [loc.lon, loc.lat] return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat) - ); + ) }) - ); + ) const available = Stores.ListStabilized( availableLayersBboxes.map((eliPolygons) => { - const loc = location.data; - const lonlat: [number, number] = [loc.lon, loc.lat]; + const loc = location.data + const lonlat: [number, number] = [loc.lon, loc.lat] const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { if (eliPolygon.geometry === null) { - return true; // global ELI-layer + return true // global ELI-layer } - return GeoOperations.inside(lonlat, eliPolygon); - }); - matching.push(...AvailableRasterLayers.globalLayers); - matching.unshift(AvailableRasterLayers.maptilerDefaultLayer, + return GeoOperations.inside(lonlat, eliPolygon) + }) + matching.push(...AvailableRasterLayers.globalLayers) + matching.unshift( + AvailableRasterLayers.maptilerDefaultLayer, AvailableRasterLayers.osmCarto, AvailableRasterLayers.maptilerCarto, AvailableRasterLayers.maptilerBackdrop, - AvailableRasterLayers.americana); - return matching; + AvailableRasterLayers.americana + ) + return matching }) - ); - return available; + ) + return available } } @@ -148,22 +150,22 @@ export class RasterLayerUtils { preferredCategory: string, ignoreLayer?: RasterLayerPolygon ): RasterLayerPolygon { - let secondBest: RasterLayerPolygon = undefined; + let secondBest: RasterLayerPolygon = undefined for (const rasterLayer of available) { if (rasterLayer === ignoreLayer) { - continue; + continue } - const p = rasterLayer.properties; + const p = rasterLayer.properties if (p.category === preferredCategory) { if (p.best) { - return rasterLayer; + return rasterLayer } if (!secondBest) { - secondBest = rasterLayer; + secondBest = rasterLayer } } } - return secondBest; + return secondBest } } @@ -179,11 +181,11 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { /** * The name of the imagery source */ - readonly name: string; + readonly name: string /** * Whether the imagery name should be translated */ - readonly i18n?: boolean; + readonly i18n?: boolean readonly type: | "tms" | "wms" @@ -191,7 +193,7 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { | "scanex" | "wms_endpoint" | "wmts" - | "vector"; /* Vector is not actually part of the ELI-spec, we add it for vector layers */ + | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */ /** * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. */ @@ -203,53 +205,53 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { | "historicphoto" | "qa" | "elevation" - | "other"; + | "other" /** * A URL template for imagery tiles */ - readonly url: string; - readonly min_zoom?: number; - readonly max_zoom?: number; + readonly url: string + readonly min_zoom?: number + readonly max_zoom?: number /** * explicit/implicit permission by the owner for use in OSM */ - readonly permission_osm?: "explicit" | "implicit" | "no"; + readonly permission_osm?: "explicit" | "implicit" | "no" /** * A URL for the license or permissions for the imagery */ - readonly license_url?: string; + readonly license_url?: string /** * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. */ - readonly privacy_policy_url?: string | boolean; + readonly privacy_policy_url?: string | boolean /** * A unique identifier for the source; used in imagery_used changeset tag */ - readonly id: string; + readonly id: string /** * A short English-language description of the source */ - readonly description?: string; + readonly description?: string /** * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. */ - readonly country_code?: string; + readonly country_code?: string /** * Whether this imagery should be shown in the default world-wide menu */ - readonly default?: boolean; + readonly default?: boolean /** * Whether this imagery is the best source for the region */ - readonly best?: boolean; + readonly best?: boolean /** * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one */ - readonly start_date?: string; + readonly start_date?: string /** * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one */ - readonly end_date?: string; + readonly end_date?: string /** * HTTP header to check for information if the tile is invalid */ @@ -259,61 +261,61 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { * via the `patternProperty` "^.*$". */ [k: string]: string[] | null - }; + } /** * 'true' if tiles are transparent and can be overlaid on another source */ - readonly overlay?: boolean & string; - readonly available_projections?: string[]; + readonly overlay?: boolean & string + readonly available_projections?: string[] readonly attribution?: { readonly url?: string readonly text?: string readonly html?: string readonly required?: boolean - }; + } /** * A URL for an image, that can be displayed in the list of imagery layers next to the name */ - readonly icon?: string; + readonly icon?: string /** * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. */ - readonly eula?: string; + readonly eula?: string /** * A URL for an image, that is displayed in the mapview for attribution */ - readonly "logo-image"?: string; + readonly "logo-image"?: string /** * Customized text for the terms of use link (default is "Background Terms of Use") */ - readonly "terms-of-use-text"?: string; + readonly "terms-of-use-text"?: string /** * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. */ - readonly "no-tile-checksum"?: string; + readonly "no-tile-checksum"?: string /** * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog */ - readonly "metadata-header"?: string; + readonly "metadata-header"?: string /** * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. */ - readonly "valid-georeference"?: boolean; + readonly "valid-georeference"?: boolean /** * Size of individual tiles delivered by a TMS service */ - readonly "tile-size"?: number; + readonly "tile-size"?: number /** * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. */ - readonly "mod-tile-features"?: string; + readonly "mod-tile-features"?: string /** * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. */ readonly "custom-http-headers"?: { readonly "header-name"?: string readonly "header-value"?: string - }; + } /** * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) */ @@ -324,17 +326,17 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { [k: string]: unknown } [k: string]: unknown - }[]; + }[] /** * format to use when connecting tile server (when using WMS_ENDPOINT type) */ - readonly format?: string; + readonly format?: string /** * If `true` transparent tiles will be requested from WMS server */ - readonly transparent?: boolean & string; + readonly transparent?: boolean & string /** * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid */ - readonly "minimum-tile-expire"?: number; + readonly "minimum-tile-expire"?: number } diff --git a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 7e33de4bb..50a90dc83 100644 --- a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -4,7 +4,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson" import { DesugaringStep, Each, Fuse, On } from "./Conversion" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" -import { del } from "idb-keyval"; +import { del } from "idb-keyval" export class UpdateLegacyLayer extends DesugaringStep< LayerConfigJson | string | { builtin; override } @@ -197,7 +197,7 @@ class UpdateLegacyTheme extends DesugaringStep { delete oldThemeConfig.socialImage } - if(oldThemeConfig.defaultBackgroundId === "osm"){ + if (oldThemeConfig.defaultBackgroundId === "osm") { console.log("Removing old background in", json.id) } diff --git a/src/UI/Base/Checkbox.svelte b/src/UI/Base/Checkbox.svelte index 92de427ac..2ff713e2e 100644 --- a/src/UI/Base/Checkbox.svelte +++ b/src/UI/Base/Checkbox.svelte @@ -1,13 +1,14 @@ +