Merge branch 'develop'
8
.github/workflows/deploy_dev.yml
vendored
|
@ -33,18 +33,14 @@ jobs:
|
|||
run: npm run generate:translations
|
||||
shell: bash
|
||||
|
||||
- name: generate layeroverview
|
||||
run: npm run reset:layeroverview
|
||||
- name: Prepare deploy
|
||||
run: npm run prepare-deploy
|
||||
shell: bash
|
||||
|
||||
- name: run tests
|
||||
run: npm run test
|
||||
shell: bash
|
||||
|
||||
- name: Prepare deploy
|
||||
run: npm run prepare-deploy
|
||||
shell: bash
|
||||
|
||||
- name: Clone deployment repo
|
||||
env:
|
||||
DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }}
|
||||
|
|
1
.gitignore
vendored
|
@ -6,6 +6,7 @@ scratch
|
|||
assets/editor-layer-index.json
|
||||
assets/generated/*
|
||||
src/assets/generated/
|
||||
assets/layers/favourite/favourite.json
|
||||
public/*.webmanifest
|
||||
/*.html
|
||||
!/index.html
|
||||
|
|
|
@ -11,3 +11,5 @@ Docs/Tools/stats/
|
|||
Docs/Layers/
|
||||
Docs/Schemas/
|
||||
Docs/TagInfo/
|
||||
src/assets/generated
|
||||
src/assets/svg
|
||||
|
|
5
.vscode/settings.json
vendored
|
@ -18,5 +18,8 @@
|
|||
"[svelte]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
"editor.formatOnSave": true,
|
||||
"files.associations": {
|
||||
"*.protojson": "json"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ Please, do reach out to the MapComplete community channel
|
|||
on [Telegram](https://t.me/MapComplete)
|
||||
or [Matrix](https://app.element.io/#/room/#MapComplete:matrix.org).
|
||||
|
||||
Get started
|
||||
-----------
|
||||
|
||||
You can create your own theme at https://mapcomplete.org/studio
|
||||
|
||||
|
||||
What is a good theme?
|
||||
---------------------
|
||||
|
@ -227,49 +232,7 @@ The entire tagRendering will thus be:
|
|||
```
|
||||
|
||||
|
||||
The template
|
||||
------------
|
||||
|
||||
[A basic template is available here](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/theme-template.json).
|
||||
|
||||
The custom theme generator
|
||||
--------------------------
|
||||
|
||||
The custom theme generator is a special page of MapComplete, where one can create their own theme. It makes it easier to
|
||||
get started.
|
||||
|
||||
However, the custom theme generator is extremely buggy and built before some updates. This means that some features
|
||||
are _not_ available through the custom theme generator. The custom theme generator is good to get the basics of the
|
||||
theme set up, but you will have to edit the raw JSON file anyway afterwards.
|
||||
|
||||
[A quick tutorial for the custom theme generator can be found here](https://www.youtube.com/watch?v=nVbFrNVPxPw).
|
||||
|
||||
Loading your theme
|
||||
------------------
|
||||
|
||||
If you have your JSON file, there are three ways to distribute your theme:
|
||||
|
||||
### a. base64 the JSON file
|
||||
|
||||
Take the entire JSON file and [base64](https://www.base64encode.org/) encode it.
|
||||
|
||||
Then open up the URL `https://mapcomplete.org?userlayout=true#<base64-encoded-json-here>`.
|
||||
|
||||
Yes, this URL will be huge; and updates are difficult to distribute as you have to send a new URL to everyone.
|
||||
|
||||
This is however excellent to have a 'quick and dirty' test version up and running as these links can be generated from the customThemeGenerator and can be quickly shared with a few other contributors.
|
||||
|
||||
You can use the community maintained [ThemeHelper](https://github.com/tordans/MapComplete-ThemeHelper) to make this process easier. This uses `lzString.compressToBase64` which is the counterpart to how MapComplete decompresses the base64 input.
|
||||
|
||||
### b. Host the JSON file
|
||||
|
||||
Host the JSON file on a publicly accessible webserver (e.g. GitHub) and open up `https://mapcomplete.org?userlayout=<url-to-the-raw.json>`
|
||||
|
||||
_Gotcha:_ Make sure the server that hosts your JSON has liberal caching settings. Otherwise one version of the file might get cached in the users' browser cache for a long time and updates are not destributed for this user.
|
||||
|
||||
### c. Make it official
|
||||
|
||||
Get your theme included into the official MapComplete:
|
||||
## Make it official
|
||||
|
||||
Did you make an awesome theme that you want to share with the OpenStreetMap community? Have it included in the main
|
||||
application. This makes sure that:
|
||||
|
|
29
Docs/UserTests/2023-12-4 User Test Favourites.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
## Task
|
||||
|
||||
Add a (specified) feature as favourite
|
||||
Find and use the list of favourites
|
||||
Determine information from this list
|
||||
Open the popup from this list
|
||||
|
||||
## Background info
|
||||
|
||||
User has used mapcomplete before
|
||||
|
||||
## Results
|
||||
|
||||
The user is asked to mark a specified bicycle shop as favourite. They find the big button to mark as favourite at the bottom.
|
||||
|
||||
When asked to select another feature, they choose a bicycle pump. When hinted that 'they can add this in a different way', they immediately select the heart title icon.
|
||||
|
||||
When asked to open the list of favourites, they open the 'hamburger'-menu. After a bit of looking, they spot the 'Your favourites'-button.
|
||||
|
||||
They are a bit confused. The specified bicycle shop is advertised as `building or wall`.
|
||||
|
||||
The bicycle pump is shown correctly, the icons are clear. When asked to open the popup for one of them, they click directly on the link.
|
||||
|
||||
## Surfaced issues
|
||||
|
||||
Due to the way the title is generated, wrong titles appeared: all titles from all layers are mixed and used as title, if the tags match. As such, the title `building or wall` appeared, as it happened to be on top and the bicycle shop had a `building~*` tag.
|
||||
|
||||
This was resolved by sorting those titles by popularity. The least occuring tags/titles are placed first, so that the most specific title is shown. This might, in some cases, still result in differing titles (e.g. if something is e.g. both a shop and a café), but this should be exceptional.
|
|
@ -1,187 +0,0 @@
|
|||
{
|
||||
"#1": "This JSON file is a small template to get you started developing a theme",
|
||||
"#2": "All lines starting with '#' are comments and can be removed in the theme if you don't need the explanation anymore",
|
||||
"#3": "Make sure to join our chat channel at https://app.element.io/#/room/#MapComplete:matrix.org for questions, sharing your theme, ...",
|
||||
"#4": "To actually load your theme: on linux: run a local webserver (e.g. `webfsd`) and go to https://mapcomplete.org/theme?userlayout=http://127.0.0.1:8080/path-to-your-theme.json",
|
||||
"#5": "If you don't know how to run a webserver: go to https://www.base64encode.org/ , copy paste this entire document in the 'encode' field and encode it;",
|
||||
"#6": "Then, go to https://mapcomplete.org/theme?userlayout=true#your-base64-encoded-file",
|
||||
"id": "template",
|
||||
"credits": "Write your name here (or remove everything)",
|
||||
"title": {
|
||||
"en": "Title of your theme",
|
||||
"#1": "You can add extra languages here (and in all translation blocks), but make sure 'en' is everywhere"
|
||||
},
|
||||
"description": {
|
||||
"en": "The welcome message goes here"
|
||||
},
|
||||
"icon": "/path/to/icon.svg OR path to an online svg, such as https://upload.wikimedia.org/wikipedia/commons/9/9f/Missing_Maps_Icon.svg",
|
||||
"startZoom": 0,
|
||||
"startLat": 0,
|
||||
"startLon": 0,
|
||||
"#7": "For more options and configuration, see the documentation in LayoutConfig.json",
|
||||
"#8": "`layers` is where most of the content will be. Either reuse an already existing layer by simply calling it's ID or define a whole new layer. An overview of builtin layers is at https://github.com/pietervdvn/MapComplete/blob/develop/Docs/BuiltinLayers.md#normal-layers",
|
||||
"layers": [
|
||||
{
|
||||
"id": "a singular noun describing the feature, in english",
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"#1": "For a description on which tags are possible, see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md",
|
||||
"and": [
|
||||
"key0=value0",
|
||||
"key1=value1",
|
||||
{
|
||||
"or": [
|
||||
"key2!=value3",
|
||||
"key3=",
|
||||
"key4~*",
|
||||
"key5~some.[regex]*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"#4": "Minzoom: only download and show if zoom >= minzoom",
|
||||
"minzoom": 12,
|
||||
"name": {
|
||||
"en": "Name of the layer, as shown in the layer selection"
|
||||
},
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Title in a popup when a feature is clicked"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "name~*",
|
||||
"then": {
|
||||
"#1": "If name is given, use name instead as popup title. Note that the translation here uses '*' instead of 'en', which'll be shown in every language",
|
||||
"*": "{name}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"#1": "Note that this is a tagRendering, but doesn't have a question field"
|
||||
},
|
||||
"allowMove": true,
|
||||
"deletion": {
|
||||
"softDeletionTags": {
|
||||
"and": [
|
||||
"razed:tourism=artwork",
|
||||
"tourism="
|
||||
]
|
||||
},
|
||||
"neededChangesets": 5
|
||||
},
|
||||
"#2": "The maprenderings describe how a feature is shown on the map",
|
||||
"mapRendering": [
|
||||
{
|
||||
"#1": "Rendering block of a mapping which is shown for points AND at the center point of a line/area",
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"icon": "circle:white;URL or path to icon.svg",
|
||||
"iconSize": "30,30,center",
|
||||
"#2": "Note: all these values can be tagrenderings too, e.g.:",
|
||||
"label": {
|
||||
"render": {
|
||||
"en": "Item"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "name~*",
|
||||
"then": {
|
||||
"*": "{name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"#1": "Rendering of a line",
|
||||
"color": "#ff0",
|
||||
"width": 5
|
||||
}
|
||||
],
|
||||
"#3": "Presets describe which new items can be added on click. Delete this block if adding a new point is not relevant",
|
||||
"presets": [
|
||||
{
|
||||
"title": {
|
||||
"en": "lowercase item"
|
||||
},
|
||||
"tags": [
|
||||
"somekey=somevalue",
|
||||
"otherkey=othervalue"
|
||||
],
|
||||
"description": "A thorough definition of what the item is, usefull if people add stuff wrongly. This is optional",
|
||||
"exampleImages": [
|
||||
"optionally add images here",
|
||||
"an image.jpg",
|
||||
"another example image of the feature.jpg"
|
||||
]
|
||||
}
|
||||
],
|
||||
"#1": "The tagrenderings are everything that must be shown and/or asked. Use a full tag-rendering section OR a single string to call a builtin tagrendering (see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/BuiltinQuestions.md)",
|
||||
"tagRenderings": [
|
||||
{
|
||||
"render": {
|
||||
"en": "This is a simple tagrendering without a question. It will always show this text as is"
|
||||
}
|
||||
},
|
||||
"images",
|
||||
"website",
|
||||
"phone",
|
||||
"opening_hours",
|
||||
"email",
|
||||
"reviews",
|
||||
{
|
||||
"render": {
|
||||
"en": "This is a simple tagrendering without a question. It will always show this text as is"
|
||||
}
|
||||
},
|
||||
{
|
||||
"render": {
|
||||
"en": "The value of some_osm_key is {some_osm_key} in this advanced tagrendering"
|
||||
},
|
||||
"question": {
|
||||
"en": "What is XYZ?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "some_osm_key",
|
||||
"#1": "Types can be found at https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialInputElements.md",
|
||||
"type": "nat"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "somekey=some_value",
|
||||
"then": {
|
||||
"en": "Text on radio button which also is shown if somekey=some_value is present on the object"
|
||||
},
|
||||
"#1": "If this option is picked as answer, these tags will be added additionally. However, if 'somekey=some_value' is present, the above rendering will be shown",
|
||||
"addExtraTags": [
|
||||
"extrakey=extravalue"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"mappings": [
|
||||
{
|
||||
"if": "somekey=some_value",
|
||||
"then": {
|
||||
"en": "Text on radio button which also is shown if somekey=some_value is present on the object"
|
||||
},
|
||||
"icon": {
|
||||
"path": "/path/to/extra-icon.svg OR url",
|
||||
"class": "medium",
|
||||
"#1": "An extra icon supporting this option"
|
||||
},
|
||||
"#1": "If this option is picked as answer, these tags will be added additionally. However, if 'somekey=some_value' is present, the above rendering will be shown",
|
||||
"addExtraTags": [
|
||||
"extrakey=extravalue"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1115,7 +1115,8 @@
|
|||
"hideInAnswer": {
|
||||
"and": [
|
||||
"advertising!=poster_box",
|
||||
"advertising!=column"
|
||||
"advertising!=column",
|
||||
"advertising!=billboard"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -212,7 +212,7 @@
|
|||
"es": "El cajero es de {operator}"
|
||||
}
|
||||
},
|
||||
"opening_hours",
|
||||
"opening_hours_24_7",
|
||||
{
|
||||
"id": "cash_out",
|
||||
"question": {
|
||||
|
|
|
@ -329,15 +329,80 @@
|
|||
"class": "medium"
|
||||
},
|
||||
"hideInAnswer": {
|
||||
"or": [
|
||||
"_country!=be",
|
||||
"and": [
|
||||
"_country!=af",
|
||||
"_country!=al",
|
||||
"_country!=dz",
|
||||
"_country!=as",
|
||||
"_country!=ad",
|
||||
"_country!=ao",
|
||||
"_country!=am",
|
||||
"_country!=aw",
|
||||
"_country!=az",
|
||||
"_country!=by",
|
||||
"_country!=bt",
|
||||
"_country!=ba",
|
||||
"_country!=bg",
|
||||
"_country!=cv",
|
||||
"_country!=td",
|
||||
"_country!=cl",
|
||||
"_country!=hr",
|
||||
"_country!=dk",
|
||||
"_country!=eg",
|
||||
"_country!=ee",
|
||||
"_country!=et",
|
||||
"_country!=fo",
|
||||
"_country!=fr",
|
||||
"_country!=ma",
|
||||
"_country!=tn",
|
||||
"_country!=pl",
|
||||
"_country!=cs",
|
||||
"_country!=pf",
|
||||
"_country!=ge",
|
||||
"_country!=gr",
|
||||
"_country!=gl",
|
||||
"_country!=gn",
|
||||
"_country!=gw",
|
||||
"_country!=is",
|
||||
"_country!=id",
|
||||
"_country!=ir",
|
||||
"_country!=jo",
|
||||
"_country!=kz",
|
||||
"_country!=kg",
|
||||
"_country!=la",
|
||||
"_country!=lv",
|
||||
"_country!=lr",
|
||||
"_country!=ly",
|
||||
"_country!=lt",
|
||||
"_country!=lu",
|
||||
"_country!=mo",
|
||||
"_country!=mr",
|
||||
"_country!=md",
|
||||
"_country!=mc",
|
||||
"_country!=mn",
|
||||
"_country!=me",
|
||||
"_country!=mz",
|
||||
"_country!=nl",
|
||||
"_country!=nc",
|
||||
"_country!=ne",
|
||||
"_country!=kp",
|
||||
"_country!=mk",
|
||||
"_country!=pt",
|
||||
"_country!=qa",
|
||||
"_country!=ro",
|
||||
"_country!=ru",
|
||||
"_country!=rw",
|
||||
"_country!=sm",
|
||||
"_country!=sk",
|
||||
"_country!=mo"
|
||||
"_country!=si",
|
||||
"_country!=kr",
|
||||
"_country!=es",
|
||||
"_country!=sr",
|
||||
"_country!=tj",
|
||||
"_country!=th",
|
||||
"_country!=tl",
|
||||
"_country!=tr",
|
||||
"_country!=tm",
|
||||
"_country!=ua",
|
||||
"_country!=uy",
|
||||
"_country!=uz",
|
||||
"_country!=vn"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -378,6 +443,52 @@
|
|||
"icon": {
|
||||
"path": "./assets/layers/charging_station/TypeE.svg",
|
||||
"class": "medium"
|
||||
},
|
||||
"hideInAnswer": {
|
||||
"and": [
|
||||
"_country!=be",
|
||||
"_country!=bj",
|
||||
"_country!=bf",
|
||||
"_country!=bi",
|
||||
"_country!=cm",
|
||||
"_country!=cf",
|
||||
"_country!=td",
|
||||
"_country!=km",
|
||||
"_country!=cz",
|
||||
"_country!=dk",
|
||||
"_country!=dj",
|
||||
"_country!=gq",
|
||||
"_country!=et",
|
||||
"_country!=fo",
|
||||
"_country!=fr",
|
||||
"_country!=gf",
|
||||
"_country!=pf",
|
||||
"_country!=gl",
|
||||
"_country!=gp",
|
||||
"_country!=gw",
|
||||
"_country!=la",
|
||||
"_country!=lr",
|
||||
"_country!=mg",
|
||||
"_country!=ml",
|
||||
"_country!=mq",
|
||||
"_country!=mr",
|
||||
"_country!=mu",
|
||||
"_country!=mc",
|
||||
"_country!=mn",
|
||||
"_country!=ma",
|
||||
"_country!=ne",
|
||||
"_country!=pl",
|
||||
"_country!=pt",
|
||||
"_country!=rw",
|
||||
"_country!=mf",
|
||||
"_country!=pm",
|
||||
"_country!=sn",
|
||||
"_country!=sk",
|
||||
"_country!=sy",
|
||||
"_country!=tl",
|
||||
"_country!=tn",
|
||||
"_country!=uz"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1031,7 +1142,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"or": [
|
||||
"and": [
|
||||
"_country!=us"
|
||||
]
|
||||
}
|
||||
|
@ -5102,16 +5213,15 @@
|
|||
"tags": [
|
||||
"amenity=charging_station",
|
||||
"motorcar=no",
|
||||
"bicycle=yes",
|
||||
"socket:typee=1"
|
||||
"bicycle=yes"
|
||||
],
|
||||
"title": {
|
||||
"en": "a charging station for electrical bikes with a normal european wall plug <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (meant to charge electrical bikes)",
|
||||
"nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (speciaal bedoeld voor fietsen)",
|
||||
"en": "a charging station for electrical bikes",
|
||||
"nl": "een oplaadpunt voor elektrische fietsen",
|
||||
"ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal<img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (destinat a carregar bicicletes elèctriques)",
|
||||
"cs": "nabíjecí stanice pro elektrokola s běžnou evropskou zástrčkou <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (určeno k nabíjení elektrických kol)",
|
||||
"da": "en ladestation til elektriske cykler med et normalt europæisk vægstik <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (beregnet til opladning af elektriske cykler)",
|
||||
"de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (zum Laden von Elektrofahrrädern)",
|
||||
"de": "eine Ladestation für Elektrofahrräder",
|
||||
"es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (pensado para cargar bicicletas eléctricas)"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -735,10 +735,12 @@
|
|||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"marker": [{
|
||||
"marker": [
|
||||
{
|
||||
"icon": "pin",
|
||||
"color": "#fff"
|
||||
},{
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"render": "./assets/themes/charging_stations/plug.svg",
|
||||
"mappings": [
|
||||
|
@ -756,9 +758,9 @@
|
|||
"then": "./assets/themes/charging_stations/car.svg"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}],
|
||||
}
|
||||
],
|
||||
"iconBadges": [
|
||||
{
|
||||
"if": {
|
||||
|
@ -802,12 +804,11 @@
|
|||
"tags": [
|
||||
"amenity=charging_station",
|
||||
"motorcar=no",
|
||||
"bicycle=yes",
|
||||
"socket:typee=1"
|
||||
"bicycle=yes"
|
||||
],
|
||||
"title": {
|
||||
"en": "charging station for electrical bikes with a normal european wall plug <img src='./assets/layers/charging_station/TypeE.svg' class='w-4 h-4 mx-1 bg-white rounded-full'/>",
|
||||
"nl": "oplaadpunt voor elektrische fietsen met een gewone, europese stekker <img src='./assets/layers/charging_station/TypeE.svg' class='w-4 h-4 mx-1 bg-white rounded-full'/>"
|
||||
"en": "charging station for electrical bikes",
|
||||
"nl": "oplaadpunt voor elektrische fietsen"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -91,7 +91,7 @@ function run(file, protojson) {
|
|||
if (e.countryWhiteList.length > 0) {
|
||||
// This is a 'hideInAnswer', thus _reverse_ logic!
|
||||
const countries = e.countryWhiteList.map(country => "_country!=" + country) //HideInAnswer if it is in the wrong country
|
||||
json["hideInAnswer"] = {or: countries}
|
||||
json["hideInAnswer"] = {and: countries} // Should be and, as we want to hide if it does not match any of the countries
|
||||
} else if (e.countryBlackList.length > 0) {
|
||||
const countries = e.countryBlackList.map(country => "_country=" + country) //HideInAnswer if it is in the wrong country
|
||||
json["hideInAnswer"] = {or: countries}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
key,image,description:en,countryWhiteList,countryBlackList,commonVoltages,commonCurrents,commonOutputs,description:nl,associatedVehicleTypes,neverAssociatedWith,extraVisualisationCondition
|
||||
socket:schuko,CEE7_4F.svg,<b>Schuko wall plug</b> without ground pin (CEE7/4 type F),be;fr;ma;tn;pl;cs;sk;mo,,230,16,3.6 kW,<b>Schuko stekker</b> zonder aardingspin (CEE7/4 type F),*,,
|
||||
socket:typee,TypeE.svg,<b>European wall plug</b> with ground pin (CEE7/4 type E),,,230,16,3 kW;22 kW;,<b>Europese stekker</b> met aardingspin (CEE7/4 type E),*,,
|
||||
socket:schuko,CEE7_4F.svg,<b>Schuko wall plug</b> without ground pin (CEE7/4 type F),af;al;dz;as;ad;ao;am;aw;az;by;bt;ba;bg;cv;td;cl;hr;dk;eg;ee;et;fo;fr;pf;ge;gr;gl;gn;gw;is;id;ir;jo;kz;kg;la;lv;lr;ly;lt;lu;mo;mr;md;mc;mn;me;mz;nl;nc;ne;kp;mk;pt;qa;ro;ru;rw;sm;sk;si;kr;es;sr;tj;th;tl;tr;tm;ua;uy;uz;vn,,230,16,3.6 kW,<b>Schuko stekker</b> zonder aardingspin (CEE7/4 type F),*,,
|
||||
socket:typee,TypeE.svg,<b>European wall plug</b> with ground pin (CEE7/4 type E),be;bj;bf;bi;cm;cf;td;km;cz;dk;dj;gq;et;fo;fr;gf;pf;gl;gp;gw;la;lr;mg;ml;mq;mr;mu;mc;mn;ma;ne;pl;pt;rw;mf;pm;sn;sk;sy;tl;tn;uz,,230,16,3 kW;22 kW;,<b>Europese stekker</b> met aardingspin (CEE7/4 type E),*,,
|
||||
socket:chademo,Chademo_type4.svg,<b>Chademo</b>,,,500,120,50 kW,<b>Chademo</b>,car;motorcar;hgv;bus,bicycle;scooter,
|
||||
socket:type1_cable,Type1_J1772.svg,<b>Type 1 with cable</b> (J1772),,,200;240,32,3.7 kW;7 kW,<b>Type 1 met kabel</b> (J1772),car;motorcar;hgv;bus,bicycle;scooter,
|
||||
socket:type1,Type1_J1772.svg,<b>Type 1 <i>without</i> cable</b> (J1772),,,200;240,32,3.7 kW;6.6 kW;7 kW;7.2 kW,<b>Type 1 <i>zonder</i> kabel</b> (J1772),car;motorcar;hgv;bus,bicycle;scooter,
|
||||
|
|
|
|
@ -343,6 +343,7 @@
|
|||
},
|
||||
"id": "Rock type (crag/rock/cliff only)"
|
||||
},
|
||||
"reviews",
|
||||
{
|
||||
"id": "default_climbing_questions",
|
||||
"builtin": [
|
||||
|
|
|
@ -129,6 +129,7 @@
|
|||
},
|
||||
"payment-options",
|
||||
"opening_hours",
|
||||
"reviews",
|
||||
{
|
||||
"id": "shoe_rental",
|
||||
"question": {
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
"natural=stone"
|
||||
]
|
||||
},
|
||||
"climbing="
|
||||
"climbing=",
|
||||
"sport!=climbing"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
18
assets/layers/drinking_water/bottle.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 474 474" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M318.002,137.333l-39.004-75.667C275.974,55.8,268.1,51,261.5,51h-49c-6.6,0-14.474,4.8-17.498,10.667l-39.004,75.667
|
||||
c-2.149,4.169-4.015,10.296-4.918,15.667h171.84C322.017,147.63,320.151,141.503,318.002,137.333z"/>
|
||||
<path d="M150.5,462c0,6.6,5.4,12,12,12h149c6.6,0,12-5.4,12-12V341.5h-173V462z"/>
|
||||
<path d="M209.5,42h55c6.6,0,12-5.4,12-12V12c0-6.6-5.4-12-12-12h-55c-6.6,0-12,5.4-12,12v18C197.5,36.6,202.9,42,209.5,42z"/>
|
||||
<path d="M327,216c1.925,0,3.5-5.4,3.5-12v-22c0-3.579-0.466-6.796-1.197-9H144.697c-0.731,2.204-1.197,5.421-1.197,9v22
|
||||
c0,6.6,1.575,12,3.5,12s3.5,1.913,3.5,4.25s-1.575,4.25-3.5,4.25s-3.5,5.4-3.5,12v22c0,6.6,1.575,12,3.5,12s3.5,1.913,3.5,4.25
|
||||
S148.925,279,147,279s-3.5,5.4-3.5,12v22c0,3.314,0.398,6.323,1.036,8.5h184.928c0.639-2.177,1.036-5.186,1.036-8.5v-22
|
||||
c0-6.6-1.575-12-3.5-12s-3.5-1.913-3.5-4.25s1.575-4.25,3.5-4.25s3.5-5.4,3.5-12v-22c0-6.6-1.575-12-3.5-12s-3.5-1.913-3.5-4.25
|
||||
S325.075,216,327,216z M234.125,294.973c-20.875,0-37.797-16.922-37.797-37.797S234.125,193,234.125,193
|
||||
s37.797,43.301,37.797,64.176S255,294.973,234.125,294.973z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
2
assets/layers/drinking_water/bottle.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Unkown
|
||||
SPDX-License-Identifier: CC0-1.0
|
18
assets/layers/drinking_water/bubbler.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 82.376 100" enable-background="new 0 0 82.376 100" xml:space="preserve"><path d="M52.074,20.308c5.59,0,10.131-4.541,10.155-10.152C62.205,4.563,57.664,0.021,52.074,0
|
||||
c-5.615,0.021-10.156,4.563-10.153,10.156C41.918,15.767,46.459,20.308,52.074,20.308L52.074,20.308z"></path><path d="M28.78,12.165c5.746-2.962,13.778,2.471,13.846,8.416v19.333l9.178,10.154c4.218,4.726-2.458,11.231-7.115,6.515
|
||||
l-10.37-11.458c-0.669-0.637-1.152-1.395-1.141-3.149v-9.233L15.04,42.517v49.145C15.045,102.762,0.011,102.76,0,91.769V33.503
|
||||
c0.011-3.246,1.297-6.405,4.506-8.253L28.78,12.165L28.78,12.165z"></path><path d="M66.41,62.012h15.966V32.96l-32.147-0.055c-4.107-0.001-5.652,4.657-3.094,7.605L66.41,62.012L66.41,62.012z"></path><path d="M57.178,31.604c0.675-0.007,1.23-0.563,1.249-1.249c-0.019-0.686-0.574-1.241-1.249-1.25
|
||||
c-0.694,0.01-1.249,0.564-1.249,1.25C55.929,31.041,56.483,31.597,57.178,31.604L57.178,31.604z"></path><path d="M57.178,30.355"></path><path d="M60.491,31.82c0.684-0.008,1.238-0.563,1.249-1.247c-0.011-0.685-0.565-1.241-1.249-1.25c-0.687,0.01-1.242,0.566-1.25,1.25
|
||||
C59.249,31.257,59.805,31.812,60.491,31.82L60.491,31.82z"></path><path d="M60.491,30.574"></path><path d="M60.708,28.13c0.68,0.016,1.234-0.54,1.249-1.197c-0.015-0.711-0.569-1.264-1.249-1.25c-0.689-0.015-1.244,0.539-1.249,1.25
|
||||
C59.464,27.59,60.019,28.146,60.708,28.13L60.708,28.13z"></path><path d="M60.708,26.934"></path><path d="M57.396,27.91c0.684,0.008,1.238-0.549,1.25-1.248c-0.012-0.67-0.566-1.225-1.25-1.25c-0.686,0.025-1.241,0.58-1.248,1.25
|
||||
C56.155,27.361,56.711,27.918,57.396,27.91L57.396,27.91z"></path><path d="M57.396,26.662"></path><path d="M58.482,24.545c0.705-0.026,1.289-0.609,1.304-1.304c-0.015-0.743-0.599-1.327-1.304-1.303
|
||||
c-0.732-0.024-1.314,0.56-1.305,1.303C57.168,23.936,57.75,24.519,58.482,24.545L58.482,24.545z"></path><path d="M58.482,23.241"></path><path d="M62.501,25.197c0.723-0.028,1.307-0.61,1.303-1.306c0.004-0.743-0.58-1.327-1.303-1.3c-0.715-0.026-1.298,0.557-1.303,1.3
|
||||
C61.203,24.587,61.786,25.169,62.501,25.197L62.501,25.197z"></path><path d="M62.501,23.892"></path><path d="M61.091,21.828c0.735-0.015,1.348-0.625,1.356-1.355c-0.009-0.781-0.621-1.394-1.356-1.414
|
||||
c-0.775,0.02-1.386,0.633-1.359,1.414C59.705,21.204,60.315,21.813,61.091,21.828L61.091,21.828z"></path><path d="M61.091,20.473"></path><path d="M65.757,24.001c0.744-0.015,1.354-0.628,1.358-1.358c-0.004-0.781-0.614-1.392-1.358-1.41
|
||||
c-0.767,0.019-1.38,0.629-1.356,1.41C64.377,23.373,64.99,23.987,65.757,24.001L65.757,24.001z"></path><path d="M65.757,22.644"></path><path d="M64.238,20.635c0.774-0.017,1.387-0.627,1.414-1.359c-0.027-0.778-0.64-1.392-1.414-1.41
|
||||
c-0.734,0.019-1.35,0.632-1.357,1.41C62.889,20.008,63.504,20.619,64.238,20.635L64.238,20.635z"></path><path d="M64.238,19.276"></path><path d="M67.931,20.635c0.846-0.01,1.521-0.687,1.521-1.521c0-0.828-0.675-1.504-1.521-1.521c-0.819,0.018-1.494,0.693-1.521,1.521
|
||||
C66.437,19.948,67.111,20.625,67.931,20.635L67.931,20.635z"></path><path d="M67.931,19.115"></path><path d="M69.125,25.306c0.819-0.012,1.494-0.688,1.519-1.52c-0.024-0.831-0.699-1.507-1.519-1.521
|
||||
c-0.846,0.015-1.519,0.69-1.519,1.521C67.606,24.618,68.279,25.293,69.125,25.306L69.125,25.306z"></path><path d="M69.125,23.786"></path><path d="M71.404,22.428c0.889-0.02,1.597-0.727,1.577-1.577c0.02-0.897-0.688-1.605-1.577-1.627
|
||||
c-0.859,0.022-1.568,0.73-1.572,1.627C69.836,21.701,70.545,22.408,71.404,22.428L71.404,22.428z"></path><path d="M71.404,20.851"></path><path d="M74.446,25.468c0.915,0.006,1.659-0.738,1.685-1.63c-0.025-0.943-0.77-1.685-1.685-1.681
|
||||
c-0.921-0.004-1.664,0.738-1.683,1.681C72.782,24.73,73.525,25.474,74.446,25.468L74.446,25.468z"></path><path d="M74.446,23.838"></path><path d="M71.568,28.399c0.925,0.006,1.666-0.736,1.686-1.628c-0.02-0.942-0.761-1.689-1.686-1.683
|
||||
c-0.912-0.006-1.656,0.741-1.626,1.683C69.912,27.663,70.656,28.405,71.568,28.399L71.568,28.399z"></path><path d="M71.568,26.771"></path></svg>
|
After Width: | Height: | Size: 4.1 KiB |
2
assets/layers/drinking_water/bubbler.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: AIGA
|
||||
SPDX-License-Identifier: CC0-1.0
|
|
@ -30,7 +30,8 @@
|
|||
{
|
||||
"or": [
|
||||
"amenity=drinking_water",
|
||||
"drinking_water=yes"
|
||||
"drinking_water=yes",
|
||||
"disused:amenity=drinking_water"
|
||||
]
|
||||
},
|
||||
"man_made!=reservoir_covered",
|
||||
|
@ -61,6 +62,11 @@
|
|||
"cs": "Pitná voda"
|
||||
}
|
||||
},
|
||||
"titleIcons": [
|
||||
"icons.defaults",
|
||||
"auto:type",
|
||||
"auto:seasonal"
|
||||
],
|
||||
"pointRendering": [
|
||||
{
|
||||
"iconBadges": [
|
||||
|
@ -68,10 +74,15 @@
|
|||
"if": {
|
||||
"or": [
|
||||
"operational_status=broken",
|
||||
"operational_status=closed"
|
||||
"operational_status=closed",
|
||||
"disused:amenity=drinking_water"
|
||||
]
|
||||
},
|
||||
"then": "close:#c33"
|
||||
},
|
||||
{
|
||||
"if": "tourism=artwork",
|
||||
"then": "circle:white;./assets/layers/artwork/artwork.svg"
|
||||
}
|
||||
],
|
||||
"iconSize": "40,40",
|
||||
|
@ -147,6 +158,10 @@
|
|||
"mappings": [
|
||||
{
|
||||
"if": "operational_status=",
|
||||
"addExtraTags": [
|
||||
"disused:amenity=",
|
||||
"amenity=drinking_water"
|
||||
],
|
||||
"then": {
|
||||
"en": "This drinking water works",
|
||||
"nl": "Deze drinkwaterfontein werkt",
|
||||
|
@ -190,6 +205,46 @@
|
|||
],
|
||||
"id": "Still in use?"
|
||||
},
|
||||
{
|
||||
"id": "type",
|
||||
"question": {
|
||||
"en": "What type of drinking water point is this?",
|
||||
"nl": "Wat voor soort drinkwaterpunt is dit?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "fountain=bubbler",
|
||||
"icon": "./assets/layers/drinking_water/bubbler.svg",
|
||||
"then": {
|
||||
"en": "This is a bubbler fountain. A water jet to drink from is sent upwards, typically controlled by a push button."
|
||||
},
|
||||
"addExtraTags": [
|
||||
"man_made="
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "fountain=bottle_refill",
|
||||
"icon": "./assets/layers/drinking_water/bottle.svg",
|
||||
"then": {
|
||||
"en": "This is a bottle refill point where the water is sent downwards, typically controlled by a push button or a motion sensor. Drinking directly from the stream might be very hard or impossible."
|
||||
},
|
||||
"addExtraTags": [
|
||||
"man_made=",
|
||||
"bottle=yes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "man_made=water_tap",
|
||||
"icon": "./assets/layers/drinking_water/tap.svg",
|
||||
"then": {
|
||||
"en": "This is a water tap. The water flows downward and the stream is controlled by a valve or push-button."
|
||||
},
|
||||
"addExtraTags": [
|
||||
"fountain="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "How easy is it to fill water bottles?",
|
||||
|
@ -235,8 +290,153 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"condition": "fountain!=bottle_refill",
|
||||
"id": "Bottle refill"
|
||||
},
|
||||
{
|
||||
"id": "fee",
|
||||
"question": {
|
||||
"en": "Is this drinking water point free to use?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "fee=no",
|
||||
"then": {
|
||||
"en": "Free to use"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "fee=yes",
|
||||
"then": {
|
||||
"en": "One needs to pay to use this drinking water point"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "seasonal",
|
||||
"question": {
|
||||
"en": "Is this drinking water point available all year round?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "seasonal=no",
|
||||
"then": {
|
||||
"en": "This drinking water point is available all around the year"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "seasonal=summer",
|
||||
"then": {
|
||||
"en": "This drinking water point is only available in summer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "seasonal=spring;summer;autumn",
|
||||
"icon": "./assets/layers/drinking_water/no_winter.svg",
|
||||
"then": {
|
||||
"en": "This drinking water point is closed during the winter"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"builtin": "opening_hours_24_7",
|
||||
"override": {
|
||||
"questionHint": {
|
||||
"en": "These are the opening hours if the drinking water fountain is operational."
|
||||
},
|
||||
"+mappings": [
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"seasonal!=no",
|
||||
"seasonal~*",
|
||||
{
|
||||
"or": [
|
||||
{
|
||||
"and": [
|
||||
"seasonal!~.*winter.*",
|
||||
"_now:date~....-(12|01|02)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal!~.*spring.*",
|
||||
"_now:date~....-(03|04|05)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal!~.*summer.*",
|
||||
"_now:date~....-(06|07|08)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal!~.*autumn.*",
|
||||
"_now:date~....-(09|10|11)-.."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"en": "This drinking water fountain is closed this season. As such, the opening hours are not shown."
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bench-artwork",
|
||||
"question": {
|
||||
"en": "Does this drinking water fountain have an artistic element?",
|
||||
"nl": "Heeft dit drinkwaterpunt een geintegreerd kunstwerk?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "tourism=artwork",
|
||||
"addExtraTags": [
|
||||
"not:tourism:artwork="
|
||||
],
|
||||
"then": {
|
||||
"en": "This drinking water point has an integrated artwork",
|
||||
"nl": "Dit drinkwaterpunt heeft een geintegreerd kunstwerk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "not:tourism:artwork=yes",
|
||||
"then": {
|
||||
"en": "This drinking water point does not have an integrated artwork",
|
||||
"nl": "Dit drinkwaterpunt heeft geen geïntegreerd kunstwerk"
|
||||
},
|
||||
"addExtraTags": [
|
||||
"tourism="
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "tourism=",
|
||||
"then": {
|
||||
"en": "This drinking water point <span class=\"subtle\">probably</span> doesn't have an integrated artwork",
|
||||
"nl": "Dit drinkwaterpunt heeft <span class=\"subtle\">waarschijnlijk</span> geen geïntegreerd kunstwerk"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
],
|
||||
"questionHint": {
|
||||
"en": "E.g. it has an integrated statue or other non-trivial, creative work",
|
||||
"nl": "Bijvoorbeeld een standbeeld of ander, niet-triviaal kunstwerk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "artwork.*artwork-question",
|
||||
"override": {
|
||||
"condition": "tourism=artwork"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "render-closest-drinking-water",
|
||||
"render": {
|
||||
|
@ -293,6 +493,19 @@
|
|||
"ca": "Es tracta d'una aixeta d'aigua o bomba d'aigua amb aigua no potable. <div class='subtle'> Per exemple les aixetes d'aigua amb aigua de pluja per aprofitar i regar les plantes properes</div>",
|
||||
"cs": "Jedná se o vodovodní kohoutek nebo vodní čerpadlo s nepitnou vodou.<div class='subtle'>Příkladem jsou vodovodní kohoutky s dešťovou vodou pro zalévání rostlin v okolí</div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"amenity=",
|
||||
"man_made=pump",
|
||||
"historic=yes",
|
||||
"drinking_water=no"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"en": "This is a historic, manual water pump where no drinking water can be found"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
[
|
||||
{
|
||||
"path": "bottle.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"Unkown"
|
||||
],
|
||||
"sources": [
|
||||
"https://www.svgrepo.com/svg/83123/water-bottle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "bubbler.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"AIGA"
|
||||
],
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org/wiki/File:Drinking_Fountain_-_The_Noun_Project.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "drips.svg",
|
||||
"license": "CC-BY-SA-4.0",
|
||||
|
@ -12,5 +32,31 @@
|
|||
"sources": [
|
||||
"https://osoc.be/editions/2020/cyclofix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "no_winter.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "tap.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"Krzysztof Franek"
|
||||
],
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org/wiki/File:Water_DIN-style.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "winter.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"sources": []
|
||||
}
|
||||
]
|
81
assets/layers/drinking_water/no_winter.svg
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg"
|
||||
version="1.1"
|
||||
width="307.91855"
|
||||
height="343.46448"
|
||||
viewBox="0 0 307.91855 343.46448"
|
||||
sodipodi:docname="no_winter.svg"
|
||||
inkscape:version="1.3.1 (1:1.3.1+202311172155+91b66b0783)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.2257936"
|
||||
inkscape:cx="190.48884"
|
||||
inkscape:cy="271.25284"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="995"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg" />
|
||||
<g
|
||||
id="g1"
|
||||
transform="rotate(-0.2875813,162.85132,95.170788)">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 5.008225,86.10653 302.13815,258.03113"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.09023,0.048073 1.65028,343.280557"
|
||||
id="path3-7"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 302.54004,85.82932 5.2907,257.54738"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 5.786561,200.08323 53.987051,25.55191 -3.26951,61.20155"
|
||||
id="path6" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 56.719332,59.041006 3.20013,59.642774 -55.391241,26.23225"
|
||||
id="path7" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 201.87442,31.964241 152.44006,65.486554 101.3842,31.580519"
|
||||
id="path8" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.26283,313.48917 -49.04644,-34.08735 -51.44157,33.31792"
|
||||
id="path9" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 300.19729,199.07429 -53.31225,26.93149 4.84196,61.09726"
|
||||
id="path10" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 251.37871,59.718988 -4.44648,59.562822 54.83066,27.38467"
|
||||
id="path11" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#000000;stroke:#e40000;stroke-width:25;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 23.115301,297.04602 290.36883,29.792506"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
2
assets/layers/drinking_water/no_winter.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||
SPDX-License-Identifier: CC0-1.0
|
73
assets/layers/drinking_water/tap.svg
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="255.66626"
|
||||
height="237.48238"
|
||||
id="svg2"
|
||||
inkscape:version="1.3.1 (1:1.3.1+202311172155+91b66b0783)"
|
||||
sodipodi:docname="tap.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata22">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs20">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : -25.017624 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="600 : -25.017624 : 1"
|
||||
inkscape:persp3d-origin="300 : -112.51762 : 1"
|
||||
id="perspective24" />
|
||||
<inkscape:perspective
|
||||
id="perspective4417"
|
||||
inkscape:persp3d-origin="0.5 : -287.18429 : 1"
|
||||
inkscape:vp_z="1 : -287.01762 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : -287.01762 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="995"
|
||||
id="namedview18"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3757615"
|
||||
inkscape:cx="118.48832"
|
||||
inkscape:cy="112.17456"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;stroke:none;stroke-width:0.5;marker:none;enable-background:accumulate"
|
||||
d="m 130.85922,0.00198911 c -7.5726,-0.12704 -13.62649,5.86286999 -16.19543,8.89080999 l -47.50163,-1.41908 -0.45904,23.8757499 48.92071,-1.92008 c 0.87811,1.09025 2.18743,2.36572 3.96541,3.50637 l 0.74163,39.73956 H 93.7514 c 0.16847,-0.33722 -1.69824,9.11586 -3.04733,11.64596 -1.34909,2.52988 -1.368597,16.006961 -28.691187,20.054861 -27.32322,4.04785 -34.366163,5.90643 -48.196573,18.55589 -13.82978,12.64952 -13.6491,36.27328 -13.6491,36.27328 L 0,188.21563 41.32432,187.88171 c 0,0 -1.21054,-4.87794 -1.21054,-9.60054 0,-4.72247 -1.386495,-16.34901 3.167475,-25.62534 4.55398,-9.27634 10.882401,-6.14303 18.978211,-7.82964 l 175.514274,-3.39564 c 0,0 2.94683,0.32094 3.93643,1.28285 0.92877,0.90277 1.30836,3.65878 1.30836,3.65878 v 30.69852 h 12.64773 l -0.16721,-124.973861 -11.97952,0.16699 0.48595,36.10628 c -1.01213,2.36131 -3.71491,2.37924 -3.71491,2.37924 l -63.39018,0.16704 c 0,0 -6.55829,0.66502 -10.26861,-3.21417 -3.71032,-3.87919 -3.04733,-15.02695 -3.04733,-15.02695 h -22.14849 l 1.56994,-40.94625 c 1.00229,-0.79886 1.84131,-1.6162 2.505,-2.37936 l 49.004,1.04365 -0.45904,-23.8344099 -47.62688,2.12886 c -2.72771,-3.16073 -8.39659,-8.56263999 -15.56981,-8.68226999 z M 18.19948,194.30829 c -5.93312,17.25329 -15.89776,20.57384 -13.14873,31.682 1.43868,5.81337 6.39567,11.81612 13.81631,11.47847 7.42128,-0.33692 15.19407,-3.04332 15.19407,-15.86164 0,-12.81822 -15.86165,-27.29883 -15.86165,-27.29883 z"
|
||||
id="path4390"
|
||||
sodipodi:nodetypes="ccccccccccccccssccaccccccccsccccccccccscsc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
2
assets/layers/drinking_water/tap.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Krzysztof Franek
|
||||
SPDX-License-Identifier: CC0-1.0
|
73
assets/layers/drinking_water/winter.svg
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg"
|
||||
version="1.1"
|
||||
width="307.54224"
|
||||
height="343.37671"
|
||||
viewBox="0 0 307.54224 343.37671"
|
||||
sodipodi:docname="no_winter.svg"
|
||||
inkscape:version="1.3.1 (1:1.3.1+202311172155+91b66b0783)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.2257936"
|
||||
inkscape:cx="190.48884"
|
||||
inkscape:cy="271.25285"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="995"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 5.008225,86.10653 302.13815,258.03113"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.09023,0.048073 1.65028,343.280557"
|
||||
id="path3-7"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 302.54004,85.82932 5.2907,257.54738"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 5.786561,200.08323 53.987051,25.55191 -3.26951,61.20155"
|
||||
id="path6" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 56.719332,59.041006 3.20013,59.642774 -55.391241,26.23225"
|
||||
id="path7" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 201.87442,31.964241 152.44006,65.486554 101.3842,31.580519"
|
||||
id="path8" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.26283,313.48917 -49.04644,-34.08735 -51.44157,33.31792"
|
||||
id="path9" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 300.19729,199.07429 -53.31225,26.93149 4.84196,61.09726"
|
||||
id="path10" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 251.37871,59.718988 -4.44648,59.562822 54.83066,27.38467"
|
||||
id="path11" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
2
assets/layers/drinking_water/winter.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||
SPDX-License-Identifier: CC0-1.0
|
|
@ -126,6 +126,16 @@
|
|||
"nl": [
|
||||
"(basis|lagere |middelbare |secondaire| secundaire)?school"
|
||||
],
|
||||
"en": [
|
||||
"east",
|
||||
"north",
|
||||
"northeast",
|
||||
"northwest",
|
||||
"south",
|
||||
"southeast",
|
||||
"southwest",
|
||||
"west"
|
||||
],
|
||||
"fr": [
|
||||
"allée (des |de la |de l'|de |du |d')?",
|
||||
"autoroute (des |de la |de l'|de |du |d')?",
|
||||
|
@ -207,11 +217,22 @@
|
|||
"en": [
|
||||
"avenue",
|
||||
"boulevard",
|
||||
"circle",
|
||||
"church",
|
||||
"drive",
|
||||
"expressway",
|
||||
"freeway",
|
||||
"highway",
|
||||
"lane",
|
||||
"parkway",
|
||||
"path",
|
||||
"plaza",
|
||||
"road",
|
||||
"square",
|
||||
"street"
|
||||
"street",
|
||||
"terrace",
|
||||
"trail",
|
||||
"turnpike"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
47
assets/layers/favourite/favourite.proto.json
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"#":"no-translations",
|
||||
"#dont-translate": "*",
|
||||
"pointRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"marker": [
|
||||
{
|
||||
"icon": {
|
||||
"render": "heart",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "_favourite=no",
|
||||
"then": "heart_outline"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": {
|
||||
"en": "A generic map layer which shows locations that a contributor marked as favourite",
|
||||
"nl": "Een laag met persoonlijke favourieten"
|
||||
},
|
||||
"name": {
|
||||
"en": "Favourites",
|
||||
"nl": "Favorieten"
|
||||
},
|
||||
"id": "favourite",
|
||||
"source": "special",
|
||||
"isShown": "_favourite=yes",
|
||||
"minzoom": 0,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Favourite location",
|
||||
"nl": "Favoriete locatie"
|
||||
}
|
||||
},
|
||||
"tagRenderings": [
|
||||
|
||||
]
|
||||
}
|
|
@ -14,7 +14,8 @@
|
|||
{
|
||||
"id": "wikipedialink",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"render": "<a href='https://wikipedia.org/wiki/{wikipedia}' target='_blank' rel='noopener'><img src='./assets/svg/wikipedia.svg' textmode='📖' alt='Wikipedia'/></a>",
|
||||
"condition": {
|
||||
|
@ -27,7 +28,7 @@
|
|||
{
|
||||
"#": "ignore-image-in-then",
|
||||
"if": "wikipedia=",
|
||||
"then": "<a href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='WD'/></a>"
|
||||
"then": "<a class='h-8' href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='WD'/></a>"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -66,10 +67,57 @@
|
|||
],
|
||||
"metacondition": "__showTimeSensitiveIcons!=no"
|
||||
},
|
||||
{
|
||||
"id": "open_until",
|
||||
"labels": [
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"#": "Titleicon showing 'open until 17:00'",
|
||||
"icon": {
|
||||
"class": "w-20 mx-1 flex items-center"
|
||||
},
|
||||
"render": "{opening_hours_state()}",
|
||||
"condition": {
|
||||
"or": [
|
||||
"seasonal=",
|
||||
"seasonal=no",
|
||||
{
|
||||
"or": [
|
||||
{
|
||||
"and": [
|
||||
"seasonal~.*winter.*",
|
||||
"_now:date~....-(12|01|02)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal~.*spring.*",
|
||||
"_now:date~....-(03|04|05)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal~.*summer.*",
|
||||
"_now:date~....-(06|07|08)-.."
|
||||
]
|
||||
},
|
||||
{
|
||||
"and": [
|
||||
"seasonal~.*autumn.*",
|
||||
"_now:date~....-(09|10|11)-.."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "phonelink",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"render": "<a href='tel:{phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>",
|
||||
"mappings": [
|
||||
|
@ -89,7 +137,8 @@
|
|||
{
|
||||
"id": "emaillink",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"render": "<a href='mailto:{email}'><img textmode='✉️' alt='email' src='./assets/layers/questions/send_email.svg'/></a>",
|
||||
"mappings": [
|
||||
|
@ -109,7 +158,8 @@
|
|||
{
|
||||
"id": "websitelink",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"render": "<a href='{website}' target='_blank' rel='noopener'><img textmode='🌐' alt='website' src='./assets/layers/icons/website.svg'/></a>",
|
||||
"condition": "website~*"
|
||||
|
@ -117,7 +167,8 @@
|
|||
{
|
||||
"id": "smokingicon",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"mappings": [
|
||||
{
|
||||
|
@ -140,6 +191,16 @@
|
|||
"render": "{share_link()}",
|
||||
"metacondition": "_supports_sharing=yes"
|
||||
},
|
||||
{
|
||||
"id": "favourite_title_icon",
|
||||
"labels": [
|
||||
"defaults"
|
||||
],
|
||||
"render": {
|
||||
"*": "{favourite_icon()}"
|
||||
},
|
||||
"metacondition": "_loggedIn=true"
|
||||
},
|
||||
{
|
||||
"id": "osmlink",
|
||||
"labels": [
|
||||
|
@ -162,7 +223,8 @@
|
|||
{
|
||||
"id": "dogicon",
|
||||
"labels": [
|
||||
"defaults"
|
||||
"defaults",
|
||||
"in_favourite"
|
||||
],
|
||||
"mappings": [
|
||||
{
|
||||
|
@ -193,6 +255,13 @@
|
|||
"class": "w-20 mx-1 flex items-center"
|
||||
},
|
||||
"render": "{rating()}"
|
||||
},
|
||||
{
|
||||
"id": "favourite_icon",
|
||||
"description": "Only for rendering",
|
||||
"condition": "_favourite=yes",
|
||||
"icon": "circle:white;heart:red",
|
||||
"metacondition": "__showTimeSensitiveIcons!=no"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
],
|
||||
"tagRenderings": [
|
||||
"images",
|
||||
"reviews",
|
||||
{
|
||||
"question": {
|
||||
"nl": "Wat is de ondergrond van deze speeltuin?",
|
||||
|
|
62
assets/svg/center.svg
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="544.02838"
|
||||
height="544.02838"
|
||||
viewBox="0 0 544.02838 544.02838"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="center.svg"
|
||||
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showguides="true"
|
||||
inkscape:zoom="0.90326851"
|
||||
inkscape:cx="393.57068"
|
||||
inkscape:cy="250.756"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="995"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1">
|
||||
<sodipodi:guide
|
||||
position="171.95879,103.32864"
|
||||
orientation="0,-1"
|
||||
id="guide4"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="271.68286,132.35281"
|
||||
orientation="1,0"
|
||||
id="guide5"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
d="m 365.63918,111.75001 h -62.375 V 15.9375 c 0,-8.75 -7,-15.9375 -15.625,-15.9375 h -31.1875 c -8.5625,0 -15.625,7.1875 -15.625,15.9375 v 95.81251 h -62.375 l 93.5625,127.75 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccsssscccc" />
|
||||
<path
|
||||
d="m 432.27837,365.63919 v -62.375 h 95.8125 c 8.75,0 15.9375,-7 15.9375,-15.625 v -31.1875 c 0,-8.5625 -7.1875,-15.625 -15.9375,-15.625 h -95.8125 v -62.375 l -127.75,93.5625 z"
|
||||
id="path1-5"
|
||||
sodipodi:nodetypes="ccsssscccc" />
|
||||
<path
|
||||
d="m 178.38918,432.27838 h 62.375 v 95.8125 c 0,8.75 7,15.9375 15.625,15.9375 h 31.1875 c 8.5625,0 15.625,-7.1875 15.625,-15.9375 v -95.8125 h 62.375 l -93.5625,-127.75 z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccsssscccc" />
|
||||
<path
|
||||
d="m 111.75,178.38919 v 62.375 H 15.9375 c -8.75,0 -15.9375,7 -15.9375,15.625 v 31.1875 c 0,8.5625 7.1875,15.625 15.9375,15.625 H 111.75 v 62.375 l 127.74999,-93.5625 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccsssscccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
2
assets/svg/center.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||
SPDX-License-Identifier: CC0-1.0
|
|
@ -153,6 +153,14 @@
|
|||
"https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "center.svg",
|
||||
"license": "CC0-1.0",
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "checkmark.svg",
|
||||
"license": "CC0-1.0",
|
||||
|
|
|
@ -69,10 +69,12 @@
|
|||
},
|
||||
"+titleIcons": [
|
||||
{
|
||||
"id": "climbing_length",
|
||||
"render": "<div class='flex' style='word-wrap: normal; padding-right: 0.25rem;'><img src='./assets/themes/climbing/height.svg' style='height: 1.75rem;'/>{climbing:length}m</div>",
|
||||
"condition": "climbing:length~*"
|
||||
},
|
||||
{
|
||||
"id": "climbing_bolts",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "__bolts_max~*",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"render": "<div class='w-8 flex justify-center rounded-right-full climbing-{__difficulty_max:char}'> {__difficulty_max}</div>"
|
||||
},
|
||||
{
|
||||
"id": "difficulty",
|
||||
"render": "<div class='flex justify-center rounded-full pl-1 pr-1 climbing-{__difficulty:char}'> {climbing:grade:french}</div>",
|
||||
"condition": "__difficulty:char~*"
|
||||
}
|
||||
|
@ -345,8 +348,7 @@
|
|||
"key": "access:description"
|
||||
}
|
||||
},
|
||||
"questions",
|
||||
"reviews"
|
||||
"questions"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -112,7 +112,15 @@
|
|||
"lineRendering": [
|
||||
{
|
||||
"width": "4",
|
||||
"color": "#00a703"
|
||||
"color": {
|
||||
"render": "#00a703",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "state=proposed",
|
||||
"then": "#f0a513"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"pointRendering": null
|
||||
|
@ -134,8 +142,9 @@
|
|||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"rcn_ref~*"
|
||||
"or": [
|
||||
"rcn_ref~*",
|
||||
"proposed:rcn_ref~*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -146,14 +155,15 @@
|
|||
"centroid"
|
||||
],
|
||||
"label": {
|
||||
"render": "<div style='position: absolute; top: -30px; right: -10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>?</div>",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "rcn_ref~*",
|
||||
"then": "<div style='position: absolute; top: -10px; right: -10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>{rcn_ref}</div>"
|
||||
"then": "<div style='position: absolute; top: -30px; right: -10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>{rcn_ref}</div>"
|
||||
},
|
||||
{
|
||||
"if": "rcn_ref=",
|
||||
"then": "<div style='position: absolute; top: -10px; right: -10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>?</div>"
|
||||
"if": "proposed:rcn_ref~*",
|
||||
"then": "<div style='position: absolute; top: -32px; right: -10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%; border-style:dotted; border-color:white; border-width: 2px'>{proposed:rcn_ref}</div>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -162,6 +172,20 @@
|
|||
"minzoom": 12,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Cycle node",
|
||||
"de": "Fahrradknotenpunkt",
|
||||
"es": "nodo ciclista",
|
||||
"nb_NO": "sykkelnode",
|
||||
"nl": "Fietsknooppunt",
|
||||
"fr": "nœud cycliste",
|
||||
"ca": "node ciclista",
|
||||
"cs": "uzel cyklu",
|
||||
"pl": "węzeł rowerowy"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "rcn_ref~*",
|
||||
"then": {
|
||||
"en": "Cycle node <strong>{rcn_ref}</strong>",
|
||||
"de": "Fahrradknotenpunkt <strong>{rcn_ref}</strong>",
|
||||
"es": "nodo ciclista <strong>{rcn_ref}</strong>",
|
||||
|
@ -173,6 +197,15 @@
|
|||
"pl": "węzeł rowerowy <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "proposed:rcn_ref~*",
|
||||
"then": {
|
||||
"en": "Proposed cycle node <strong>{proposed:rcn_ref}</strong>",
|
||||
"nl": "Voorgesteld fietsknooppunt <strong>{proposed:rcn_ref}</strong>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "node-rxn_ref",
|
||||
|
@ -197,7 +230,8 @@
|
|||
"nl": "Dit fietsknooppunt heeft referentienummer {rcn_ref}",
|
||||
"de": "Knotenpunktnummer {rcn_ref} des Fahrradknotenpunktnetzwerks",
|
||||
"cs": "Tento cyklistický uzel má referenční číslo {rcn_ref}"
|
||||
}
|
||||
},
|
||||
"condition": "rcn_ref~*"
|
||||
},
|
||||
{
|
||||
"builtin": "survey_date",
|
||||
|
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 17 KiB |
|
@ -542,6 +542,9 @@
|
|||
"leisure=garden",
|
||||
"garden:type=facade_garden"
|
||||
],
|
||||
"snapToLayer": [
|
||||
"walls_and_buildings"
|
||||
],
|
||||
"title": {
|
||||
"nl": "een geveltuintje",
|
||||
"en": "a facade garden",
|
||||
|
|
|
@ -1,33 +1,13 @@
|
|||
{
|
||||
"id": "mapcomplete-changes",
|
||||
"title": {
|
||||
"en": "Changes made with MapComplete",
|
||||
"ca": "Canvis fets amb MapComplete",
|
||||
"cs": "Změny provedené pomocí MapComplete",
|
||||
"de": "Mit MapComplete erstellte Änderungen",
|
||||
"es": "Cambios realizados con MapComplete",
|
||||
"fr": "Changements faits avec MapComplete",
|
||||
"nl": "Wijzigingen gemaakt met MapComplete",
|
||||
"pl": "Zmiany wprowadzone za pomocą MapComplete"
|
||||
"en": "Changes made with MapComplete"
|
||||
},
|
||||
"shortDescription": {
|
||||
"en": "Show changes made with MapComplete",
|
||||
"ca": "Mostra els canvis fets amb MapComplete",
|
||||
"cs": "Zobrazení změn provedených pomocí nástroje MapComplete",
|
||||
"de": "Mit MapComplete erstellte Änderungen anzeigen",
|
||||
"es": "Mostrar cambios realizados con MapComplete",
|
||||
"nl": "Toon wijzigingen gemaakt met MapComplete",
|
||||
"pl": "Pokaż zmiany wprowadzone za pomocą MapComplete"
|
||||
"en": "Shows changes made by MapComplete"
|
||||
},
|
||||
"description": {
|
||||
"en": "This maps shows all the changes made with MapComplete",
|
||||
"ca": "Aquest mapa mostra tots els canvis fets amb MapComplete",
|
||||
"cs": "Tato mapa zobrazuje všechny změny provedené pomocí MapComplete",
|
||||
"de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
|
||||
"es": "Este mapa muestra todos los cambios realizados con MapComplete",
|
||||
"fr": "Cette carte montre tous les changements faits avec MapComplete",
|
||||
"nl": "Deze kaart toont alle wijzigingen die met MapComplete gemaakt werden",
|
||||
"pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete"
|
||||
"en": "This maps shows all the changes made with MapComplete"
|
||||
},
|
||||
"icon": "./assets/svg/logo.svg",
|
||||
"hideFromOverview": true,
|
||||
|
@ -40,13 +20,7 @@
|
|||
{
|
||||
"id": "mapcomplete-changes",
|
||||
"name": {
|
||||
"en": "Changeset centers",
|
||||
"ca": "Centre del conjunt de canvis",
|
||||
"cs": "Centrum změn",
|
||||
"de": "Zentrum der Änderungssätze",
|
||||
"es": "Centro del conjunto de cambios",
|
||||
"nl": "Centerpunt van changeset",
|
||||
"pl": "Centra zmian"
|
||||
"en": "Changeset centers"
|
||||
},
|
||||
"minzoom": 0,
|
||||
"source": {
|
||||
|
@ -57,85 +31,41 @@
|
|||
},
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Changeset for {theme}",
|
||||
"ca": "Conjunt de canvis per a {theme}",
|
||||
"cs": "Změna pro {theme}",
|
||||
"de": "Änderungssatz für {theme}",
|
||||
"es": "Conjunto de cambios para {theme}",
|
||||
"fr": "Groupe de modifications pour {theme}",
|
||||
"pl": "Zestaw zmian dla {theme}"
|
||||
"en": "Changeset for {theme}"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"en": "Show all MapComplete changes",
|
||||
"ca": "Mostra tots els canvis de MapComplete",
|
||||
"cs": "Zobrazit všechny změny MapComplete",
|
||||
"de": "Alle MapComplete-Änderungen anzeigen",
|
||||
"es": "Mostrar todos los cambios de MapComplete",
|
||||
"nl": "Toon alle MapComplete wijzigingen",
|
||||
"pl": "Wyświetl wszystkie zmiany MapComplete"
|
||||
"en": "Shows all MapComplete changes"
|
||||
},
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "show_changeset_id",
|
||||
"render": {
|
||||
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"ca": "Conjunt de canvi <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"cs": "Změny <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"es": "Conjunto de cambios <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"fr": "Groupe de modifications <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
||||
"pl": "Zestaw zmian <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
|
||||
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contributor",
|
||||
"question": {
|
||||
"en": "Which contributor made this change?",
|
||||
"ca": "Quin col·laborador va fer aquest canvi?",
|
||||
"cs": "Který přispěvatel tuto změnu provedl?",
|
||||
"de": "Wer hat diese Änderung vorgenommen?",
|
||||
"es": "¿Qué contribuidor hizo este cambio?",
|
||||
"fr": "Quel contributeur a fait cette modification ?",
|
||||
"nl": "Welke bijdrager maakte deze wijziging?",
|
||||
"pl": "Który współautor dokonał tej zmiany?"
|
||||
"en": "What contributor did make this change?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "user"
|
||||
},
|
||||
"render": {
|
||||
"en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"ca": "Canvi fet per <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"cs": "Změna provedená <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"de": "Änderung von <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"es": "Cambio realizado por <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"fr": "Modification faite par <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"nl": "Wijziging gemaakt door <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
|
||||
"pl": "Zmiana dokonana przez <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
|
||||
"en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "theme-id",
|
||||
"question": {
|
||||
"en": "What theme was used to make this change?",
|
||||
"ca": "Quin tema es va utilitzar per fer aquest canvi?",
|
||||
"cs": "Jaké téma bylo použito k provedení této změny?",
|
||||
"de": "Welches Thema wurde für diese Änderung verwendet?",
|
||||
"es": "¿Qué tema se utilizó para realizar este cambio?",
|
||||
"fr": "Quel thème a été utilisé pour faire cette modification ?",
|
||||
"pl": "Jakiego tematu użyto do wprowadzenia tej zmiany?"
|
||||
"en": "What theme was used to make this change?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "theme"
|
||||
},
|
||||
"render": {
|
||||
"en": "Change with theme <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"ca": "Canvi amb el tema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"cs": "Změna s motivem <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"de": "Geändert mit Thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"es": "Cambio con tema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"fr": "Modifié avec le thème <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
|
||||
"pl": "Zmiana za pomocą motywu <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
|
||||
"en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -144,45 +74,19 @@
|
|||
"key": "locale"
|
||||
},
|
||||
"question": {
|
||||
"en": "What locale (language) was this change made in?",
|
||||
"ca": "Amb quina configuració regional (idioma) s'ha fet aquest canvi?",
|
||||
"cs": "V jakém národním prostředí (jazyce) byla tato změna provedena?",
|
||||
"de": "In welcher Benutzersprache wurde diese Änderung vorgenommen?",
|
||||
"es": "¿En qué configuración regional (idioma) se realizó este cambio?",
|
||||
"fr": "En quelle langue est-ce que ce changement a été fait ?",
|
||||
"nl": "In welke locale (taal) werd deze wijziging gemaakt?",
|
||||
"pl": "W jakim języku wprowadzono tę zmianę?"
|
||||
"en": "What locale (language) was this change made in?"
|
||||
},
|
||||
"render": {
|
||||
"en": "User locale is {locale}",
|
||||
"ca": "La configuració regional de l'usuari és {locale}",
|
||||
"cs": "Uživatelské prostředí je {locale}",
|
||||
"de": "Benutzersprache {locale}",
|
||||
"es": "La configuración regional del usuario es {locale}",
|
||||
"nl": "De gebruikerstaal is {locale}",
|
||||
"pl": "Ustawienia regionalne użytkownika to {locale}"
|
||||
"en": "User locale is {locale}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "host",
|
||||
"render": {
|
||||
"en": "Change made with <a href='{host}'>{host}</a>",
|
||||
"ca": "Canviat fet amb <a href='{host}'>{host}</a>",
|
||||
"cs": "Změna provedená pomocí <a href='{host}'>{host}</a>",
|
||||
"de": "Geändert über <a href='{host}'>{host}</a>",
|
||||
"es": "Cambio realizado con <a href='{host}'>{host}</a>",
|
||||
"fr": "Modification faite avec <a href='{host}'>{host}</a>",
|
||||
"nl": "Wijziging gemaakt met <a href='{host}'>{host}</a>",
|
||||
"pl": "Zmiana dokonana w <a href='{host}'>{host}</a>"
|
||||
"en": "Change with with <a href='{host}'>{host}</a>"
|
||||
},
|
||||
"question": {
|
||||
"en": "What host (website) was this change made with?",
|
||||
"ca": "Amb quin amfitrió (lloc web) es va fer aquest canvi?",
|
||||
"cs": "U jakého hostitele (webové stránky) byla tato změna provedena?",
|
||||
"de": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?",
|
||||
"es": "¿Con qué host (página web) se realizó este cambio?",
|
||||
"nl": "Met welke host (website) werd deze wijziging gemaakt?",
|
||||
"pl": "Na jakim hoście (stronie internetowej) dokonano tej zmiany?"
|
||||
"en": "What host (website) was this change made with?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "host"
|
||||
|
@ -203,22 +107,10 @@
|
|||
{
|
||||
"id": "version",
|
||||
"question": {
|
||||
"en": "What version of MapComplete was used to make this change?",
|
||||
"ca": "Quina versió de MapComplete es va utilitzar per fer aquest canvi?",
|
||||
"cs": "Jaká verze aplikace MapComplete byla použita k provedení této změny?",
|
||||
"de": "Mit welcher Version von MapComplete wurde diese Änderung gemacht?",
|
||||
"es": "¿Qué versión de MapComplete se usó para realizar este cambio?",
|
||||
"fr": "Quelle version de MapComplete a été utilisée pour faire cette modification ?",
|
||||
"pl": "Która wersja MapComplete została wykorzystana, aby zrobić tę zmianę?"
|
||||
"en": "What version of MapComplete was used to make this change?"
|
||||
},
|
||||
"render": {
|
||||
"en": "Made with {editor}",
|
||||
"ca": "Fet amb {editor}",
|
||||
"cs": "Vyrobeno pomocí {editor}",
|
||||
"de": "Erstellt mit {editor}",
|
||||
"es": "Realizado con {editor}",
|
||||
"fr": "Fait avec {editor}",
|
||||
"pl": "Zrobione za pomocą {editor}"
|
||||
"en": "Made with {editor}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "editor"
|
||||
|
@ -392,6 +284,10 @@
|
|||
"if": "theme=kerbs_and_crossings",
|
||||
"then": "./assets/layers/kerbs/KerbIcon.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=mapcomplete-changes",
|
||||
"then": "./assets/svg/logo.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=maproulette",
|
||||
"then": "./assets/layers/maproulette/logomark.svg"
|
||||
|
@ -564,13 +460,23 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Theme name contains {search}",
|
||||
"ca": "El nom del tema conté {search}",
|
||||
"cs": "Název motivu obsahuje {search}",
|
||||
"de": "Themenname enthält {search}",
|
||||
"es": "El nombre del tema contiene {search}",
|
||||
"nl": "Themenaam bevat {search}",
|
||||
"pl": "Nazwa tematu zawiera {search}"
|
||||
"en": "Themename contains {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "theme-not-search",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!~i~.*{search}.*",
|
||||
"fields": [
|
||||
{
|
||||
"name": "search"
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Themename does <b>not</b> contain {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -586,13 +492,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Made by contributor {search}",
|
||||
"ca": "Fet pel col·laborador {search}",
|
||||
"cs": "Vytvořil přispěvatel {search}",
|
||||
"de": "Erstellt von {search}",
|
||||
"es": "Hecho por el colaborador {search}",
|
||||
"nl": "Gemaakt door bijdrager {search}",
|
||||
"pl": "Wykonane przez współautora {search}"
|
||||
"en": "Made by contributor {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -608,13 +508,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "<b>Not</b> made by contributor {search}",
|
||||
"ca": "<b>No</b> fet pel col·laborador {search}",
|
||||
"cs": "<b>Není</b> vytvořeno přispěvatelem {search}",
|
||||
"de": "<b>Nicht</b> erstellt von {search}",
|
||||
"es": "<b>No</b> hecho por el colaborador {search}",
|
||||
"nl": "<b>Niet</b> gemaakt door bijdrager {search}",
|
||||
"pl": "<b>Nie</b> wykonane przez współautora {search}"
|
||||
"en": "<b>Not</b> made by contributor {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -631,13 +525,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Made before {search}",
|
||||
"ca": "Fet abans de {search}",
|
||||
"cs": "Vytvořeno před {search}",
|
||||
"de": "Erstellt vor {search}",
|
||||
"es": "Hecho antes de {search}",
|
||||
"nl": "Gemaakt voor {search}",
|
||||
"pl": "Stworzone przed {search}"
|
||||
"en": "Made before {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -654,13 +542,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Made after {search}",
|
||||
"ca": "Fet després de {search}",
|
||||
"cs": "Vytvořeno po {search}",
|
||||
"de": "Erstellt nach {search}",
|
||||
"es": "Hecho después de {search}",
|
||||
"nl": "Gemaakt na {search}",
|
||||
"pl": "Stworzone po {search}"
|
||||
"en": "Made after {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -676,14 +558,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "User language (iso-code) {search}",
|
||||
"ca": "Idioma de l'usuari (codi iso) {search}",
|
||||
"cs": "Jazyk uživatele (iso-kód) {search}",
|
||||
"de": "Benutzersprache (ISO-Code) {search}",
|
||||
"es": "Use idioma (ISO-code) {search}",
|
||||
"fr": "Langage utilisateur (code-ISO) {search}",
|
||||
"nl": "De taal van de bijdrager is {search}",
|
||||
"pl": "Język użytkownika (kod iso) {search}"
|
||||
"en": "User language (iso-code) {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -699,13 +574,7 @@
|
|||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Made with host {search}",
|
||||
"ca": "Fet amb l'amfitrió {search}",
|
||||
"cs": "Vytvořeno pomocí hostitele {search}",
|
||||
"de": "Erstellt mit Host {search}",
|
||||
"es": "Hecho con el host {search}",
|
||||
"nl": "Gemaakt met host {search}",
|
||||
"pl": "Wykonane z hostem {search}"
|
||||
"en": "Made with host {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -716,14 +585,29 @@
|
|||
{
|
||||
"osmTags": "add-image>0",
|
||||
"question": {
|
||||
"en": "Changeset added at least one image",
|
||||
"ca": "El conjunt de canvis ha afegit almenys una imatge",
|
||||
"cs": "Sada změn přidala alespoň jeden obrázek",
|
||||
"de": "Im Änderungssatz wurde mindestens ein Bild hinzugefügt",
|
||||
"es": "El conjunto de cambios ha añadido al menos una imagen",
|
||||
"fr": "Le groupe de modifications a ajouté au moins une image",
|
||||
"nl": "Changeset bevat minstens één afbeelding",
|
||||
"pl": "Zestaw zmian dodał co najmniej jedno zdjęcie"
|
||||
"en": "Changeset added at least one image"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "exclude_grb",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!=grb",
|
||||
"question": {
|
||||
"en": "Exclude GRB theme"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "exclude_etymology",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!=etymology",
|
||||
"question": {
|
||||
"en": "Exclude etymology theme"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -738,13 +622,7 @@
|
|||
{
|
||||
"id": "link_to_more",
|
||||
"render": {
|
||||
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
|
||||
"ca": "Es pot trobar més estadística <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>aquí</a>",
|
||||
"cs": "Další statistiky najdete <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
|
||||
"de": "Mehr Statistiken gibt es <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>",
|
||||
"es": "Puede encontrar más estadísticas <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>aquí</a>",
|
||||
"fr": "D'autres statistiques sont disponibles <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>ici</a>",
|
||||
"pl": "Więcej statystyk można znaleźć <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>tutaj</a>"
|
||||
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -151,6 +151,22 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "theme-not-search",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!~i~.*{search}.*",
|
||||
"fields": [
|
||||
{
|
||||
"name": "search"
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Themename does <b>not</b> contain {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "created_by",
|
||||
"options": [
|
||||
|
@ -259,6 +275,28 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "exclude_grb",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!=grb",
|
||||
"question": {
|
||||
"en": "Exclude GRB theme"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "exclude_etymology",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "theme!=etymology",
|
||||
"question": {
|
||||
"en": "Exclude etymology theme"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -166,31 +166,31 @@
|
|||
{
|
||||
"if": "sidewalk:left|right=yes",
|
||||
"then": {
|
||||
"en": "Yes, there is a sidewalk on this side of the road",
|
||||
"de": "Ja, es gibt einen Bürgersteig auf dieser Straßenseite",
|
||||
"da": "Ja, der er et fortov på denne side af vejen",
|
||||
"nl": "Ja, er is een stoep aan deze kant van de weg",
|
||||
"fr": "Oui, il y a un trottoir de ce côté de la route",
|
||||
"ca": "Sí, hi ha una vorera a aquest costat del carrer",
|
||||
"es": "Sí, hay una acera en este lado de la calle",
|
||||
"cs": "Ano, na této straně silnice je chodník",
|
||||
"it": "Sì, c'è un marciapiede su questo lato della strada",
|
||||
"pl": "Tak, jest chodnik z boku drogi"
|
||||
"en": "There is a sidewalk on this side of the road",
|
||||
"de": "Es gibt einen Bürgersteig auf dieser Straßenseite",
|
||||
"da": "Der er et fortov på denne side af vejen",
|
||||
"nl": "Er is een stoep aan deze kant van de weg",
|
||||
"fr": "Il y a un trottoir de ce côté de la route",
|
||||
"ca": "Hi ha una vorera a aquest costat del carrer",
|
||||
"es": "Hay una acera en este lado de la calle",
|
||||
"cs": "Na této straně silnice je chodník",
|
||||
"it": "C'è un marciapiede su questo lato della strada",
|
||||
"pl": "Jest chodnik z boku drogi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "sidewalk:left|right=no",
|
||||
"then": {
|
||||
"en": "No, there is no sidewalk to walk on",
|
||||
"de": "Nein, es gibt keinen Bürgersteig für Fußgänger",
|
||||
"da": "Nej, der er ikke noget fortov at gå på",
|
||||
"nl": "Nee, er is geen stoep om op te lopen",
|
||||
"fr": "Non, il n'y a pas de trottoir où marcher",
|
||||
"ca": "No, no hi ha vorera per la que caminar",
|
||||
"es": "No, no hay acera por la que caminar",
|
||||
"cs": "Ne, není tu žádný chodník",
|
||||
"it": "No, non c'è un marciapiede su cui camminare",
|
||||
"pl": "Nie, nie ma chodnika, którym można chodzić"
|
||||
"en": "There is no sidewalk to walk on",
|
||||
"de": "Es gibt keinen Bürgersteig für Fußgänger",
|
||||
"da": "Der er ikke noget fortov at gå på",
|
||||
"nl": "Er is geen stoep om op te lopen",
|
||||
"fr": "Il n'y a pas de trottoir où marcher",
|
||||
"ca": "No hi ha vorera per la que caminar",
|
||||
"es": "No hay acera por la que caminar",
|
||||
"cs": "Není tu žádný chodník",
|
||||
"it": "Non c'è un marciapiede su cui camminare",
|
||||
"pl": "Nie ma chodnika, którym można chodzić"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -35,10 +35,15 @@
|
|||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"network=rwn",
|
||||
"network:type=node_network",
|
||||
{
|
||||
"or": [
|
||||
"route=hiking",
|
||||
"route=foot"
|
||||
]
|
||||
},
|
||||
"network=rwn",
|
||||
"network:type=node_network"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minzoom": 12,
|
||||
|
@ -72,7 +77,15 @@
|
|||
"lineRendering": [
|
||||
{
|
||||
"width": "4",
|
||||
"color": "#452b29"
|
||||
"color": {
|
||||
"render": "#452b29",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "state=proposed",
|
||||
"then": "#f0a513"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"pointRendering": null,
|
||||
|
@ -123,8 +136,9 @@
|
|||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"rwn_ref~*"
|
||||
"or": [
|
||||
"rwn_ref~*",
|
||||
"proposed:rwn_ref~*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -135,14 +149,15 @@
|
|||
"centroid"
|
||||
],
|
||||
"label": {
|
||||
"render": "<div style='position: absolute; top: -30px; right: -10px; color: white; background-color: #452b29; width: 20px; height: 20px; border-radius: 100%'>?</div>",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "rwn_ref~*",
|
||||
"then": "<div style='position: absolute; top: -10px; right: -10px; color: white; background-color: #452b29; width: 20px; height: 20px; border-radius: 100%'>{rwn_ref}</div>"
|
||||
"then": "<div style='position: absolute; top: -30px; right: -10px; color: white; background-color: #452b29; width: 20px; height: 20px; border-radius: 100%'>{rwn_ref}</div>"
|
||||
},
|
||||
{
|
||||
"if": "rwn_ref=",
|
||||
"then": "<div style='position: absolute; top: -10px; right: -10px; color: white; background-color: #452b29; width: 20px; height: 20px; border-radius: 100%'>?</div>"
|
||||
"if": "proposed:rwn_ref~*",
|
||||
"then": "<div style='position: absolute; top: -31px; right: -10px; color: white; background-color: #452b29; width: 22px; height: 22px; border-radius: 100%; border-style:dotted; border-color:white; border-width: 2px'>{proposed:rwn_ref}</div>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -151,11 +166,27 @@
|
|||
"minzoom": 12,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Walking node",
|
||||
"nl": "Wandelknooppunt"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "rwn_ref~*",
|
||||
"then": {
|
||||
"en": "Walking node <strong>{rwn_ref}</strong>",
|
||||
"nl": "Wandelknooppunt <strong>{rwn_ref}</strong>",
|
||||
"de": "Wanderknoten <strong>{rwn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "proposed:rwn_ref~*",
|
||||
"then": {
|
||||
"en": "Proposed walking node <strong>{proposed:rwn_ref}</strong>",
|
||||
"nl": "Voorgesteld wandelknooppunt <strong>{proposed:rwn_ref}</strong>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "node-rwn_ref",
|
||||
|
@ -179,7 +210,8 @@
|
|||
"en": "This walking node has reference number {rwn_ref}",
|
||||
"nl": "Dit wandelknooppunt heeft referentienummer {rwn_ref}",
|
||||
"de": "Dieser Wanderknoten hat die Referenznummer {rwn_ref}"
|
||||
}
|
||||
},
|
||||
"condition": "rwn_ref~*"
|
||||
},
|
||||
{
|
||||
"builtin": "survey_date",
|
||||
|
|
|
@ -50,6 +50,22 @@
|
|||
"panelIntro": "<h3>Your personal theme</h3>Activate your favourite layers from all the official themes",
|
||||
"reload": "Reload the data"
|
||||
},
|
||||
"favouritePoi": {
|
||||
"button": {
|
||||
"isFavourite": "This location is currently marked as favourite and will show up on all thematic maps of MapComplete you visit.",
|
||||
"markAsFavouriteTitle": "Mark this location as favourite location",
|
||||
"markDescription": "Add this location to a personal list of your favourites",
|
||||
"unmark": "Remove from your personal list of favourites",
|
||||
"unmarkNotDeleted": "This point will not be deleted and still be visible on the appropriate map for you and others"
|
||||
},
|
||||
"downloadGeojson": "Download your favourites as geojson",
|
||||
"downloadGpx": "Download your favourites as GPX",
|
||||
"intro": "You marked {length} locations as a favourite location.",
|
||||
"introPrivacy": "This list is only visible to you",
|
||||
"loginToSeeList": "Login to see the list of locations you marked as favourite",
|
||||
"tab": "Your favourites",
|
||||
"title": "Your favourite locations"
|
||||
},
|
||||
"flyer": {
|
||||
"aerial": "This map uses a different background, namely aerial imagery by Agentschap Informatie Vlaanderen",
|
||||
"callToAction": "Test it on mapcomplete.org",
|
||||
|
@ -404,6 +420,7 @@
|
|||
"key": "Key combination",
|
||||
"openLayersPanel": "Opens the layers and filters panel",
|
||||
"selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers",
|
||||
"selectFavourites": "Open the favourites page",
|
||||
"selectItem": "Select the POI which is closest to the map center (crosshair). Only when in keyboard navigation is used",
|
||||
"selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers",
|
||||
"selectMapnik": "Set the background layer to OpenStreetMap-carto",
|
||||
|
@ -419,6 +436,7 @@
|
|||
"isDeleted": "Deleted",
|
||||
"nearby": {
|
||||
"link": "This picture shows the object",
|
||||
"noNearbyImages": "No nearby images were found",
|
||||
"seeNearby": "Browse and link nearby pictures",
|
||||
"title": "Nearby streetview imagery"
|
||||
},
|
||||
|
|
|
@ -1993,7 +1993,7 @@
|
|||
"name": "Ladestationen",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (zum Laden von Elektrofahrrädern)"
|
||||
"title": "eine Ladestation für Elektrofahrräder"
|
||||
},
|
||||
"1": {
|
||||
"title": "Eine Ladestation für Elektrofahrzeuge"
|
||||
|
|
|
@ -1993,7 +1993,7 @@
|
|||
"name": "Charging stations",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "a charging station for electrical bikes with a normal european wall plug <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (meant to charge electrical bikes)"
|
||||
"title": "a charging station for electrical bikes"
|
||||
},
|
||||
"1": {
|
||||
"title": "a charging station for cars"
|
||||
|
@ -4218,6 +4218,9 @@
|
|||
},
|
||||
"1": {
|
||||
"then": "This is a water tap or water pump with non-drinkable water.<div class='subtle'>Examples are water taps with rain water to tap water for nearby plants</div>"
|
||||
},
|
||||
"2": {
|
||||
"then": "This is a historic, manual water pump where no drinking water can be found"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4256,8 +4259,72 @@
|
|||
"question": "Is this drinking water spot still operational?",
|
||||
"render": "The operational status is <i>{operational_status}</i>"
|
||||
},
|
||||
"bench-artwork": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This drinking water point has an integrated artwork"
|
||||
},
|
||||
"1": {
|
||||
"then": "This drinking water point does not have an integrated artwork"
|
||||
},
|
||||
"2": {
|
||||
"then": "This drinking water point <span class=\"subtle\">probably</span> doesn't have an integrated artwork"
|
||||
}
|
||||
},
|
||||
"question": "Does this drinking water fountain have an artistic element?",
|
||||
"questionHint": "E.g. it has an integrated statue or other non-trivial, creative work"
|
||||
},
|
||||
"fee": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Free to use"
|
||||
},
|
||||
"1": {
|
||||
"then": "One needs to pay to use this drinking water point"
|
||||
}
|
||||
},
|
||||
"question": "Is this drinking water point free to use?"
|
||||
},
|
||||
"opening_hours_24_7": {
|
||||
"override": {
|
||||
"+mappings": {
|
||||
"0": {
|
||||
"then": "This drinking water fountain is closed this season. As such, the opening hours are not shown."
|
||||
}
|
||||
},
|
||||
"questionHint": "These are the opening hours if the drinking water fountain is operational."
|
||||
}
|
||||
},
|
||||
"render-closest-drinking-water": {
|
||||
"render": "<a href='#{_closest_other_drinking_water_id}'>There is another drinking water fountain at {_closest_other_drinking_water_distance} meters</a>"
|
||||
},
|
||||
"seasonal": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This drinking water point is available all around the year"
|
||||
},
|
||||
"1": {
|
||||
"then": "This drinking water point is only available in summer"
|
||||
},
|
||||
"2": {
|
||||
"then": "This drinking water point is closed during the winter"
|
||||
}
|
||||
},
|
||||
"question": "Is this drinking water point available all year round?"
|
||||
},
|
||||
"type": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This is a bubbler fountain. A water jet to drink from is sent upwards, typically controlled by a push button."
|
||||
},
|
||||
"1": {
|
||||
"then": "This is a bottle refill point where the water is sent downwards, typically controlled by a push button or a motion sensor. Drinking directly from the stream might be very hard or impossible."
|
||||
},
|
||||
"2": {
|
||||
"then": "This is a water tap. The water flows downward and the stream is controlled by a valve or push-button."
|
||||
}
|
||||
},
|
||||
"question": "What type of drinking water point is this?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
|
|
|
@ -1842,7 +1842,7 @@
|
|||
"name": "Oplaadpunten",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (speciaal bedoeld voor fietsen)"
|
||||
"title": "een oplaadpunt voor elektrische fietsen"
|
||||
},
|
||||
"1": {
|
||||
"title": "een oplaadstation voor elektrische auto's"
|
||||
|
@ -4092,8 +4092,26 @@
|
|||
"question": "Is deze drinkwaterkraan nog steeds werkende?",
|
||||
"render": "Deze waterkraan-status is <i>{operational_status}</i>"
|
||||
},
|
||||
"bench-artwork": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Dit drinkwaterpunt heeft een geintegreerd kunstwerk"
|
||||
},
|
||||
"1": {
|
||||
"then": "Dit drinkwaterpunt heeft geen geïntegreerd kunstwerk"
|
||||
},
|
||||
"2": {
|
||||
"then": "Dit drinkwaterpunt heeft <span class=\"subtle\">waarschijnlijk</span> geen geïntegreerd kunstwerk"
|
||||
}
|
||||
},
|
||||
"question": "Heeft dit drinkwaterpunt een geintegreerd kunstwerk?",
|
||||
"questionHint": "Bijvoorbeeld een standbeeld of ander, niet-triviaal kunstwerk"
|
||||
},
|
||||
"render-closest-drinking-water": {
|
||||
"render": "<a href='#{_closest_other_drinking_water_id}'>Er bevindt zich een ander drinkwaterpunt op {_closest_other_drinking_water_distance} meter</a>"
|
||||
},
|
||||
"type": {
|
||||
"question": "Wat voor soort drinkwaterpunt is dit?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
|
|
|
@ -509,7 +509,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "node ciclista <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "node ciclista <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "node ciclista"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -860,49 +865,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Fet pel col·laborador {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>No</b> fet pel col·laborador {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Fet abans de {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Fet després de {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Idioma de l'usuari (codi iso) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Fet amb l'amfitrió {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "El conjunt de canvis ha afegit almenys una imatge"
|
||||
|
@ -1159,10 +1164,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Sí, hi ha una vorera a aquest costat del carrer"
|
||||
"then": "Hi ha una vorera a aquest costat del carrer"
|
||||
},
|
||||
"1": {
|
||||
"then": "No, no hi ha vorera per la que caminar"
|
||||
"then": "No hi ha vorera per la que caminar"
|
||||
},
|
||||
"2": {
|
||||
"then": "Hi ha una vorera mapejada separadament per on caminar"
|
||||
|
|
|
@ -519,7 +519,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "uzel cyklu <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "uzel cyklu <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "uzel cyklu"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
|
@ -895,49 +900,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Vytvořil přispěvatel {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>Není</b> vytvořeno přispěvatelem {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Vytvořeno před {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Vytvořeno po {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Jazyk uživatele (iso-kód) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Vytvořeno pomocí hostitele {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Sada změn přidala alespoň jeden obrázek"
|
||||
|
@ -1194,10 +1199,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Ano, na této straně silnice je chodník"
|
||||
"then": "Na této straně silnice je chodník"
|
||||
},
|
||||
"1": {
|
||||
"then": "Ne, není tu žádný chodník"
|
||||
"then": "Není tu žádný chodník"
|
||||
},
|
||||
"2": {
|
||||
"then": "Na mapě je vyznačen samostatný chodník"
|
||||
|
|
|
@ -806,10 +806,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Ja, der er et fortov på denne side af vejen"
|
||||
"then": "Der er et fortov på denne side af vejen"
|
||||
},
|
||||
"1": {
|
||||
"then": "Nej, der er ikke noget fortov at gå på"
|
||||
"then": "Der er ikke noget fortov at gå på"
|
||||
},
|
||||
"2": {
|
||||
"then": "Der er et særskilt kortlagt fortov at gå på"
|
||||
|
|
|
@ -524,7 +524,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Fahrradknotenpunkt <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Fahrradknotenpunkt <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "Fahrradknotenpunkt"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
|
@ -900,49 +905,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Erstellt von {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>Nicht</b> erstellt von {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Erstellt vor {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Erstellt nach {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Benutzersprache (ISO-Code) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Erstellt mit Host {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Im Änderungssatz wurde mindestens ein Bild hinzugefügt"
|
||||
|
@ -1199,10 +1204,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Ja, es gibt einen Bürgersteig auf dieser Straßenseite"
|
||||
"then": "Es gibt einen Bürgersteig auf dieser Straßenseite"
|
||||
},
|
||||
"1": {
|
||||
"then": "Nein, es gibt keinen Bürgersteig für Fußgänger"
|
||||
"then": "Es gibt keinen Bürgersteig für Fußgänger"
|
||||
},
|
||||
"2": {
|
||||
"then": "Es gibt einen separat kartierten Bürgersteig für Fußgänger"
|
||||
|
@ -1422,7 +1427,11 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Wanderknoten <strong>{rwn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Wanderknoten <strong>{rwn_ref}</strong>"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
|
|
|
@ -524,7 +524,15 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Cycle node <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Cycle node <strong>{rcn_ref}</strong>"
|
||||
},
|
||||
"1": {
|
||||
"then": "Proposed cycle node <strong>{proposed:rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "Cycle node"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
|
@ -903,46 +911,67 @@
|
|||
"1": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made by contributor {search}"
|
||||
"question": "Theme name does <b>not</b> contain {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>Not</b> made by contributor {search}"
|
||||
"question": "Made by contributor {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made before {search}"
|
||||
"question": "<b>Not</b> made by contributor {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made after {search}"
|
||||
"question": "Made before {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "User language (iso-code) {search}"
|
||||
"question": "Made after {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made with host {search}"
|
||||
"question": "User language (iso-code) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made with host {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Changeset added at least one image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Made with host {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Changeset added at least one image"
|
||||
|
@ -1199,10 +1228,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Yes, there is a sidewalk on this side of the road"
|
||||
"then": "There is a sidewalk on this side of the road"
|
||||
},
|
||||
"1": {
|
||||
"then": "No, there is no sidewalk to walk on"
|
||||
"then": "There is no sidewalk to walk on"
|
||||
},
|
||||
"2": {
|
||||
"then": "There is a separately mapped sidewalk to walk on"
|
||||
|
@ -1422,7 +1451,15 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Walking node <strong>{rwn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Walking node <strong>{rwn_ref}</strong>"
|
||||
},
|
||||
"1": {
|
||||
"then": "Proposed walking node <strong>{proposed:rwn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "Walking node"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
|
|
|
@ -509,7 +509,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "nodo ciclista <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "nodo ciclista <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "nodo ciclista"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -864,49 +869,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Hecho por el colaborador {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>No</b> hecho por el colaborador {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Hecho antes de {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Hecho después de {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Use idioma (ISO-code) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Hecho con el host {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "El conjunto de cambios ha añadido al menos una imagen"
|
||||
|
@ -1163,10 +1168,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Sí, hay una acera en este lado de la calle"
|
||||
"then": "Hay una acera en este lado de la calle"
|
||||
},
|
||||
"1": {
|
||||
"then": "No, no hay acera por la que caminar"
|
||||
"then": "No hay acera por la que caminar"
|
||||
},
|
||||
"2": {
|
||||
"then": "Hay una acera mapeada por separado por la que caminar"
|
||||
|
|
|
@ -506,7 +506,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "nœud cycliste <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "nœud cycliste <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "nœud cycliste"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -842,14 +847,14 @@
|
|||
"layers": {
|
||||
"0": {
|
||||
"filter": {
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Langage utilisateur (code-ISO) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Le groupe de modifications a ajouté au moins une image"
|
||||
|
@ -1102,10 +1107,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Oui, il y a un trottoir de ce côté de la route"
|
||||
"then": "Il y a un trottoir de ce côté de la route"
|
||||
},
|
||||
"1": {
|
||||
"then": "Non, il n'y a pas de trottoir où marcher"
|
||||
"then": "Il n'y a pas de trottoir où marcher"
|
||||
},
|
||||
"2": {
|
||||
"then": "Il y a un trottoir où marcher cartographié séparément"
|
||||
|
|
|
@ -599,10 +599,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Sì, c'è un marciapiede su questo lato della strada"
|
||||
"then": "C'è un marciapiede su questo lato della strada"
|
||||
},
|
||||
"1": {
|
||||
"then": "No, non c'è un marciapiede su cui camminare"
|
||||
"then": "Non c'è un marciapiede su cui camminare"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -283,7 +283,12 @@
|
|||
"1": {
|
||||
"name": "noder",
|
||||
"title": {
|
||||
"render": "sykkelnode <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "sykkelnode <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "sykkelnode"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -475,7 +475,15 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Fietsknooppunt <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Fietsknooppunt <strong>{rcn_ref}</strong>"
|
||||
},
|
||||
"1": {
|
||||
"then": "Voorgesteld fietsknooppunt <strong>{proposed:rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "Fietsknooppunt"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -875,49 +883,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Gemaakt door bijdrager {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>Niet</b> gemaakt door bijdrager {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Gemaakt voor {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Gemaakt na {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "De taal van de bijdrager is {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Gemaakt met host {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Changeset bevat minstens één afbeelding"
|
||||
|
@ -1156,10 +1164,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Ja, er is een stoep aan deze kant van de weg"
|
||||
"then": "Er is een stoep aan deze kant van de weg"
|
||||
},
|
||||
"1": {
|
||||
"then": "Nee, er is geen stoep om op te lopen"
|
||||
"then": "Er is geen stoep om op te lopen"
|
||||
},
|
||||
"2": {
|
||||
"then": "Er is een apart ingetekende stoep om op te lopen"
|
||||
|
@ -1424,7 +1432,15 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Wandelknooppunt <strong>{rwn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Wandelknooppunt <strong>{rwn_ref}</strong>"
|
||||
},
|
||||
"1": {
|
||||
"then": "Voorgesteld wandelknooppunt <strong>{proposed:rwn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "Wandelknooppunt"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -509,7 +509,12 @@
|
|||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "węzeł rowerowy <strong>{rcn_ref}</strong>"
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "węzeł rowerowy <strong>{rcn_ref}</strong>"
|
||||
}
|
||||
},
|
||||
"render": "węzeł rowerowy"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -864,49 +869,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Wykonane przez współautora {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"3": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "<b>Nie</b> wykonane przez współautora {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"4": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Stworzone przed {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"5": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Stworzone po {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"6": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Język użytkownika (kod iso) {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Wykonane z hostem {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Zestaw zmian dodał co najmniej jedno zdjęcie"
|
||||
|
@ -1163,10 +1168,10 @@
|
|||
"1": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Tak, jest chodnik z boku drogi"
|
||||
"then": "Jest chodnik z boku drogi"
|
||||
},
|
||||
"1": {
|
||||
"then": "Nie, nie ma chodnika, którym można chodzić"
|
||||
"then": "Nie ma chodnika, którym można chodzić"
|
||||
},
|
||||
"2": {
|
||||
"then": "Jest oddzielnie oznaczony chodnik"
|
||||
|
|
902
package-lock.json
generated
13
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mapcomplete",
|
||||
"version": "0.35.1",
|
||||
"version": "0.36.3",
|
||||
"repository": "https://github.com/pietervdvn/MapComplete",
|
||||
"description": "A small website to edit OSM easily",
|
||||
"bugs": "https://github.com/pietervdvn/MapComplete/issues",
|
||||
|
@ -62,11 +62,12 @@
|
|||
"generate:schemas": "ts2json-schema -p src/Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && echo 'tsjson is done' && vite-node scripts/fixSchemas.ts ",
|
||||
"fix:schemas": "vite-node scripts/fixSchemas.ts ",
|
||||
"watch:schemas": "cd Models/ThemeConfig/Json & ls | entr -s 'npm run generate:schemas' ",
|
||||
"generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js",
|
||||
"generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak",
|
||||
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
|
||||
"generate:stats": "vite-node scripts/GenerateSeries.ts",
|
||||
"reset:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && npm run generate:layeroverview && vite-node scripts/generateLayerOverview.ts -- --force",
|
||||
"generate": "mkdir -p ./assets/generated; npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run reset:layeroverview; npm run generate:service-worker",
|
||||
"reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview",
|
||||
"prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json",
|
||||
"generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker",
|
||||
"generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -",
|
||||
"prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
|
||||
"lint": "npm run lint:prettier && npm run lint:eslint",
|
||||
|
@ -133,6 +134,7 @@
|
|||
"opening_hours": "^3.6.0",
|
||||
"osm-auth": "^2.2.0",
|
||||
"osmtogeojson": "^3.0.0-beta.5",
|
||||
"panzoom": "^9.4.3",
|
||||
"papaparse": "^5.3.1",
|
||||
"pic4carto": "^2.1.15",
|
||||
"prompt-sync": "^4.2.0",
|
||||
|
@ -141,6 +143,7 @@
|
|||
"svg-path-parser": "^1.1.0",
|
||||
"tailwind-merge": "^1.13.1",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"trap-focus-svelte": "^1.0.1",
|
||||
"vite-node": "^0.28.3",
|
||||
"vitest": "^0.28.3",
|
||||
"wikibase-sdk": "^7.14.0",
|
||||
|
@ -178,7 +181,7 @@
|
|||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"read-file": "^0.2.0",
|
||||
"sass": "^1.58.0",
|
||||
"sharp": "^0.30.5",
|
||||
"sharp": "^0.32.6",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^3.0.2",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
|
|
|
@ -729,6 +729,14 @@ video {
|
|||
bottom: 0px;
|
||||
}
|
||||
|
||||
.right-4 {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.top-4 {
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.right-1\/3 {
|
||||
right: 33.333333%;
|
||||
}
|
||||
|
@ -745,6 +753,10 @@ video {
|
|||
top: 2.5rem;
|
||||
}
|
||||
|
||||
.left-1\/4 {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
@ -765,10 +777,6 @@ video {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
@ -781,6 +789,10 @@ video {
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
@ -793,10 +805,58 @@ video {
|
|||
margin: 0.125rem;
|
||||
}
|
||||
|
||||
.m-11 {
|
||||
margin: 2.75rem;
|
||||
}
|
||||
|
||||
.m-20 {
|
||||
margin: 5rem;
|
||||
}
|
||||
|
||||
.m-9 {
|
||||
margin: 2.25rem;
|
||||
}
|
||||
|
||||
.m-5 {
|
||||
margin: 1.25rem;
|
||||
}
|
||||
|
||||
.m-14 {
|
||||
margin: 3.5rem;
|
||||
}
|
||||
|
||||
.m-52 {
|
||||
margin: 13rem;
|
||||
}
|
||||
|
||||
.m-36 {
|
||||
margin: 9rem;
|
||||
}
|
||||
|
||||
.m-72 {
|
||||
margin: 18rem;
|
||||
}
|
||||
|
||||
.m-6 {
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
.m-32 {
|
||||
margin: 8rem;
|
||||
}
|
||||
|
||||
.m-44 {
|
||||
margin: 11rem;
|
||||
}
|
||||
|
||||
.m-28 {
|
||||
margin: 7rem;
|
||||
}
|
||||
|
||||
.m-7 {
|
||||
margin: 1.75rem;
|
||||
}
|
||||
|
||||
.m-px {
|
||||
margin: 1px;
|
||||
}
|
||||
|
@ -841,6 +901,10 @@ video {
|
|||
margin-right: 3rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
@ -877,10 +941,6 @@ video {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
@ -929,6 +989,10 @@ video {
|
|||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.mt-12 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.mr-12 {
|
||||
margin-right: 3rem;
|
||||
}
|
||||
|
@ -1025,14 +1089,14 @@ video {
|
|||
height: 6rem;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-32 {
|
||||
height: 8rem;
|
||||
}
|
||||
|
@ -1084,6 +1148,10 @@ video {
|
|||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
@ -1104,14 +1172,14 @@ video {
|
|||
height: 10rem;
|
||||
}
|
||||
|
||||
.h-80 {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
.h-64 {
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.h-80 {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
.max-h-12 {
|
||||
max-height: 3rem;
|
||||
}
|
||||
|
@ -1120,6 +1188,10 @@ video {
|
|||
max-height: 6rem;
|
||||
}
|
||||
|
||||
.max-h-64 {
|
||||
max-height: 16rem;
|
||||
}
|
||||
|
||||
.max-h-7 {
|
||||
max-height: 1.75rem;
|
||||
}
|
||||
|
@ -1194,6 +1266,18 @@ video {
|
|||
width: 50%;
|
||||
}
|
||||
|
||||
.w-14 {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
@ -1207,10 +1291,6 @@ video {
|
|||
width: 12rem;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.max-w-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -1285,6 +1365,10 @@ video {
|
|||
appearance: none;
|
||||
}
|
||||
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
|
@ -1345,6 +1429,10 @@ video {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
@ -1437,6 +1525,14 @@ video {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
.justify-self-start {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.justify-self-end {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -1593,6 +1689,10 @@ video {
|
|||
border-style: dotted;
|
||||
}
|
||||
|
||||
.border-none {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.border-black {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
||||
|
@ -1647,6 +1747,10 @@ video {
|
|||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white\/50 {
|
||||
background-color: rgb(255 255 255 / 0.5);
|
||||
}
|
||||
|
||||
.bg-red-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||
|
@ -1692,14 +1796,14 @@ video {
|
|||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.p-0\.5 {
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.p-0 {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.p-0\.5 {
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 3rem;
|
||||
}
|
||||
|
@ -1764,6 +1868,10 @@ video {
|
|||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
@ -1772,10 +1880,6 @@ video {
|
|||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pb-10 {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
@ -1784,6 +1888,10 @@ video {
|
|||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1945,6 +2053,10 @@ video {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.opacity-100 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
||||
|
@ -2046,6 +2158,12 @@ video {
|
|||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
}
|
||||
|
||||
.transition-colors {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, -webkit-transform, -webkit-filter, -webkit-backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
|
@ -2054,10 +2172,8 @@ video {
|
|||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition-colors {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
|
@ -2126,6 +2242,10 @@ body {
|
|||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.focusable {
|
||||
/* Not a 'real' class, but rather an indication to FloatOver and ModalRight to, when they open, grab the focus */
|
||||
}
|
||||
|
||||
svg,
|
||||
img {
|
||||
box-sizing: content-box;
|
||||
|
@ -2327,6 +2447,16 @@ button.disabled:hover, .button.disabled:hover {
|
|||
color: unset;
|
||||
}
|
||||
|
||||
button.link {
|
||||
border: none;
|
||||
text-decoration: underline;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
button.link:hover {
|
||||
color:unset;
|
||||
}
|
||||
|
||||
.interactive button.disabled svg path, .interactive .button.disabled svg path {
|
||||
fill: var(--interactive-foreground) !important;
|
||||
}
|
||||
|
@ -2696,6 +2826,15 @@ a.link-underline {
|
|||
min-height: 8rem;
|
||||
}
|
||||
|
||||
.max-w-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.hover\:bg-white:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-indigo-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(199 210 254 / var(--tw-bg-opacity));
|
||||
|
@ -2850,10 +2989,6 @@ a.link-underline {
|
|||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.md\:p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.md\:p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,15 @@ mkdir dist 2> /dev/null
|
|||
mkdir dist/assets 2> /dev/null
|
||||
|
||||
|
||||
export NODE_OPTIONS="--max-old-space-size=8192"
|
||||
export NODE_OPTIONS="--max-old-space-size=16384"
|
||||
|
||||
# This script ends every line with '&&' to chain everything. A failure will thus stop the build
|
||||
npm run generate:editor-layer-index &&
|
||||
npm run generate &&
|
||||
npm run prep:layeroverview &&
|
||||
npm run generate && # includes a single "refresh:layeroverview". Resetting the files is unnecessary as they are not in there in the first place
|
||||
npm run refresh:layeroverview && # run refresh:layeroverview a second time to propagate all calls
|
||||
npm run refresh:layeroverview && # run refresh:layeroverview a third time to fix some issues with the favourite layer all calls
|
||||
|
||||
npm run generate:layouts
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
|
@ -48,9 +52,10 @@ else
|
|||
exit 1
|
||||
fi
|
||||
|
||||
export NODE_OPTIONS=--max-old-space-size=7000
|
||||
export NODE_OPTIONS=--max-old-space-size=16000
|
||||
which vite
|
||||
vite build --sourcemap
|
||||
vite --version
|
||||
vite build # --sourcemap
|
||||
# Copy the layer files, as these might contain assets (e.g. svgs)
|
||||
cp -r assets/layers/ dist/assets/layers/
|
||||
cp -r assets/themes/ dist/assets/themes/
|
||||
|
|
335
scripts/generateFavouritesLayer.ts
Normal file
|
@ -0,0 +1,335 @@
|
|||
import Script from "./Script"
|
||||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs"
|
||||
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
|
||||
import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts"
|
||||
import { Utils } from "../src/Utils"
|
||||
import { AddEditingElements } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
|
||||
import {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
||||
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
|
||||
|
||||
export class GenerateFavouritesLayer extends Script {
|
||||
private readonly layers: LayerConfigJson[] = []
|
||||
|
||||
constructor() {
|
||||
super("Prepares the 'favourites'-layer")
|
||||
const allThemes = new AllKnownLayoutsLazy(false).values()
|
||||
for (const theme of allThemes) {
|
||||
if (theme.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
for (const layer of theme.layers) {
|
||||
if (!layer.source) {
|
||||
continue
|
||||
}
|
||||
if (layer.source.geojsonSource) {
|
||||
continue
|
||||
}
|
||||
const layerConfig = AllSharedLayers.getSharedLayersConfigs().get(layer.id)
|
||||
if (!layerConfig) {
|
||||
continue
|
||||
}
|
||||
this.layers.push(layerConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sortMappings(mappings: MappingConfigJson[]): MappingConfigJson[] {
|
||||
const sortedMappings: MappingConfigJson[] = [...mappings]
|
||||
sortedMappings.sort((a, b) => {
|
||||
const aTag = TagUtils.Tag(a.if)
|
||||
const bTag = TagUtils.Tag(b.if)
|
||||
const aPop = TagUtils.GetPopularity(aTag)
|
||||
const bPop = TagUtils.GetPopularity(bTag)
|
||||
return aPop - bPop
|
||||
})
|
||||
|
||||
return sortedMappings
|
||||
}
|
||||
|
||||
private addTagRenderings(proto: LayerConfigJson) {
|
||||
const blacklistedIds = new Set([
|
||||
"images",
|
||||
"questions",
|
||||
"mapillary",
|
||||
"leftover-questions",
|
||||
"last_edit",
|
||||
"minimap",
|
||||
"move-button",
|
||||
"delete-button",
|
||||
"all-tags",
|
||||
"all_tags",
|
||||
...AddEditingElements.addedElements,
|
||||
])
|
||||
|
||||
const generatedTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = []
|
||||
const trPerId = new Map<
|
||||
string,
|
||||
{ conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson }
|
||||
>()
|
||||
for (const layerConfig of this.layers) {
|
||||
if (!layerConfig.tagRenderings) {
|
||||
continue
|
||||
}
|
||||
for (const tagRendering of layerConfig.tagRenderings) {
|
||||
if (typeof tagRendering === "string") {
|
||||
if (blacklistedIds.has(tagRendering)) {
|
||||
continue
|
||||
}
|
||||
generatedTagRenderings.push(tagRendering)
|
||||
blacklistedIds.add(tagRendering)
|
||||
continue
|
||||
}
|
||||
if (tagRendering["builtin"]) {
|
||||
continue
|
||||
}
|
||||
const id = tagRendering.id
|
||||
if (blacklistedIds.has(id)) {
|
||||
continue
|
||||
}
|
||||
if (trPerId.has(id)) {
|
||||
const old = trPerId.get(id).tr
|
||||
|
||||
// We need to figure out if this was a 'recycled' tag rendering or just happens to have the same id
|
||||
function isSame(fieldName: string) {
|
||||
return old[fieldName]?.["en"] === tagRendering[fieldName]?.["en"]
|
||||
}
|
||||
|
||||
const sameQuestion = isSame("question") && isSame("render")
|
||||
if (!sameQuestion) {
|
||||
const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
|
||||
newTr.id = layerConfig.id + "_" + newTr.id
|
||||
if (blacklistedIds.has(newTr.id)) {
|
||||
continue
|
||||
}
|
||||
newTr.condition = {
|
||||
and: Utils.NoNull([newTr.condition, layerConfig.source["osmTags"]]),
|
||||
}
|
||||
generatedTagRenderings.push(newTr)
|
||||
blacklistedIds.add(newTr.id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (!trPerId.has(id)) {
|
||||
const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
|
||||
generatedTagRenderings.push(newTr)
|
||||
trPerId.set(newTr.id, { tr: newTr, conditions: [] })
|
||||
}
|
||||
const conditions = trPerId.get(id).conditions
|
||||
if (tagRendering["condition"]) {
|
||||
conditions.push({
|
||||
and: [tagRendering["condition"], layerConfig.source["osmTags"]],
|
||||
})
|
||||
} else {
|
||||
conditions.push(layerConfig.source["osmTags"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const { tr, conditions } of Array.from(trPerId.values())) {
|
||||
const optimized = TagUtils.optimzeJson({ or: conditions })
|
||||
if (optimized === true) {
|
||||
continue
|
||||
}
|
||||
if (optimized === false) {
|
||||
throw "Optimized into 'false', this is weird..."
|
||||
}
|
||||
tr.condition = optimized
|
||||
}
|
||||
|
||||
const allTags: QuestionableTagRenderingConfigJson = {
|
||||
id: "all-tags",
|
||||
render: { "*": "{all_tags()}" },
|
||||
|
||||
metacondition: {
|
||||
or: [
|
||||
"__featureSwitchIsDebugging=true",
|
||||
"mapcomplete-show_tags=full",
|
||||
"mapcomplete-show_debug=yes",
|
||||
],
|
||||
},
|
||||
}
|
||||
proto.tagRenderings = [
|
||||
"images",
|
||||
...generatedTagRenderings,
|
||||
...proto.tagRenderings,
|
||||
"questions",
|
||||
allTags,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* const titleIcons = new GenerateFavouritesLayer().generateTitleIcons()
|
||||
* JSON.stringify(titleIcons).indexOf("icons.defaults") // => -1
|
||||
* */
|
||||
private generateTitleIcons(): TagRenderingConfigJson[] {
|
||||
let iconsLibrary: Map<string, TagRenderingConfigJson[]> = new Map<
|
||||
string,
|
||||
TagRenderingConfigJson[]
|
||||
>()
|
||||
const path = "./src/assets/generated/layers/icons.json"
|
||||
if (existsSync(path)) {
|
||||
const config = <LayerConfigJson>JSON.parse(readFileSync(path, "utf8"))
|
||||
for (const tagRendering of config.tagRenderings) {
|
||||
const qtr = <QuestionableTagRenderingConfigJson>tagRendering
|
||||
const id = qtr.id
|
||||
if (id) {
|
||||
iconsLibrary.set(id, [qtr])
|
||||
}
|
||||
for (const label of tagRendering["labels"] ?? []) {
|
||||
if (!iconsLibrary.has(label)) {
|
||||
iconsLibrary.set(label, [])
|
||||
}
|
||||
iconsLibrary.get(label).push(qtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
let titleIcons: TagRenderingConfigJson[] = []
|
||||
const seenTitleIcons = new Set<string>()
|
||||
for (const layer of this.layers) {
|
||||
for (const titleIcon of layer.titleIcons) {
|
||||
if (typeof titleIcon === "string") {
|
||||
continue
|
||||
}
|
||||
if (titleIcon["labels"]?.indexOf("defaults") >= 0) {
|
||||
continue
|
||||
}
|
||||
if (titleIcon.id === "rating") {
|
||||
if (!seenTitleIcons.has("rating")) {
|
||||
titleIcons.unshift(...iconsLibrary.get("rating"))
|
||||
seenTitleIcons.add("rating")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (seenTitleIcons.has(titleIcon.id)) {
|
||||
continue
|
||||
}
|
||||
seenTitleIcons.add(titleIcon.id)
|
||||
console.log("Adding ", titleIcon.id)
|
||||
titleIcons.push(titleIcon)
|
||||
}
|
||||
}
|
||||
titleIcons.push(...(iconsLibrary.get("defaults") ?? []))
|
||||
return titleIcons
|
||||
}
|
||||
|
||||
private addTitle(proto: LayerConfigJson) {
|
||||
let mappings: MappingConfigJson[] = []
|
||||
for (const layer of this.layers) {
|
||||
const t = layer.title
|
||||
const tags: TagConfigJson = layer.source["osmTags"]
|
||||
if (!t) {
|
||||
continue
|
||||
}
|
||||
if (typeof t === "string") {
|
||||
mappings.push({ if: tags, then: t })
|
||||
} else if (t["render"] !== undefined || t["mappings"] !== undefined) {
|
||||
const tr = <TagRenderingConfigJson>t
|
||||
for (let i = 0; i < (tr.mappings ?? []).length; i++) {
|
||||
const mapping = tr.mappings[i]
|
||||
const optimized = TagUtils.optimzeJson({
|
||||
and: [mapping.if, tags],
|
||||
})
|
||||
if (optimized === false) {
|
||||
console.warn(
|
||||
"The following tags yielded 'false':",
|
||||
JSON.stringify(mapping.if),
|
||||
JSON.stringify(tags)
|
||||
)
|
||||
continue
|
||||
}
|
||||
if (optimized === true) {
|
||||
console.error(
|
||||
"The following tags yielded 'false':",
|
||||
JSON.stringify(mapping.if),
|
||||
JSON.stringify(tags)
|
||||
)
|
||||
throw "Tags for title optimized to true"
|
||||
}
|
||||
|
||||
if (!mapping.then) {
|
||||
throw (
|
||||
"The title has a missing 'then' for mapping " +
|
||||
i +
|
||||
" in layer " +
|
||||
layer.id
|
||||
)
|
||||
}
|
||||
mappings.push({
|
||||
if: optimized,
|
||||
then: mapping.then,
|
||||
})
|
||||
}
|
||||
if (tr.render) {
|
||||
mappings.push({
|
||||
if: tags,
|
||||
then: <Translatable>tr.render,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
mappings.push({ if: tags, then: <Record<string, string>>t })
|
||||
}
|
||||
}
|
||||
|
||||
mappings = this.sortMappings(mappings)
|
||||
|
||||
if (proto.title["mappings"]) {
|
||||
mappings.unshift(...proto.title["mappings"])
|
||||
}
|
||||
if (proto.title["render"]) {
|
||||
mappings.push({
|
||||
if: "id~*",
|
||||
then: proto.title["render"],
|
||||
})
|
||||
}
|
||||
|
||||
for (const mapping of mappings) {
|
||||
const opt = TagUtils.optimzeJson(mapping.if)
|
||||
if (typeof opt === "boolean") {
|
||||
continue
|
||||
}
|
||||
mapping.if = opt
|
||||
}
|
||||
|
||||
proto.title = {
|
||||
mappings,
|
||||
}
|
||||
}
|
||||
|
||||
async main(args: string[]): Promise<void> {
|
||||
console.log("Generating the favourite layer: stealing _all_ tagRenderings")
|
||||
const proto = this.readLayer("favourite/favourite.proto.json")
|
||||
this.addTagRenderings(proto)
|
||||
this.addTitle(proto)
|
||||
proto.titleIcons = this.generateTitleIcons()
|
||||
const targetContent = JSON.stringify(proto, null, " ")
|
||||
const path = "./assets/layers/favourite/favourite.json"
|
||||
if (existsSync(path)) {
|
||||
if (readFileSync(path, "utf8") === targetContent) {
|
||||
console.log(
|
||||
"Already existing favourite layer is identical to the generated one, not writing"
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
console.log("Written favourite layer to", path)
|
||||
writeFileSync(path, targetContent)
|
||||
}
|
||||
|
||||
private readLayer(path: string): LayerConfigJson {
|
||||
try {
|
||||
return JSON.parse(readFileSync("./assets/layers/" + path, "utf8"))
|
||||
} catch (e) {
|
||||
console.error("Could not read ./assets/layers/" + path)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new GenerateFavouritesLayer().run()
|
|
@ -4,7 +4,7 @@ import { RegexTag } from "../src/Logic/Tags/RegexTag"
|
|||
import { ImmutableStore } from "../src/Logic/UIEventSource"
|
||||
import { BBox } from "../src/Logic/BBox"
|
||||
import * as fs from "fs"
|
||||
import { writeFileSync } from "fs"
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
||||
import { Feature } from "geojson"
|
||||
import ScriptUtils from "./ScriptUtils"
|
||||
import { Imgur } from "../src/Logic/ImageProviders/Imgur"
|
||||
|
@ -181,6 +181,58 @@ export default class GenerateImageAnalysis extends Script {
|
|||
}
|
||||
}
|
||||
|
||||
async downloadViews(datapath: string): Promise<void> {
|
||||
const { allImages, imageSource } = this.loadImageUrls(datapath)
|
||||
console.log("Detected", allImages.size, "images")
|
||||
const results: [string, number][] = []
|
||||
const today = new Date().toISOString().substring(0, "YYYY-MM-DD".length)
|
||||
const viewDir = datapath + "/views_" + today
|
||||
if (!existsSync(viewDir)) {
|
||||
mkdirSync(viewDir)
|
||||
}
|
||||
const targetpath = datapath + "/views.csv"
|
||||
|
||||
const total = allImages.size
|
||||
let dloaded = 0
|
||||
let skipped = 0
|
||||
let err = 0
|
||||
for (const image of Array.from(allImages)) {
|
||||
const cachedView = viewDir + "/" + image.replace(/\//g, "_")
|
||||
let attribution: LicenseInfo
|
||||
if (existsSync(cachedView)) {
|
||||
attribution = JSON.parse(readFileSync(cachedView, "utf8"))
|
||||
skipped++
|
||||
} else {
|
||||
try {
|
||||
attribution = await Imgur.singleton.DownloadAttribution(image)
|
||||
await ScriptUtils.sleep(500)
|
||||
writeFileSync(cachedView, JSON.stringify(attribution))
|
||||
dloaded++
|
||||
} catch (e) {
|
||||
err++
|
||||
continue
|
||||
}
|
||||
}
|
||||
results.push([image, attribution.views])
|
||||
if (dloaded % 50 === 0) {
|
||||
console.log({
|
||||
dloaded,
|
||||
skipped,
|
||||
total,
|
||||
err,
|
||||
progress: Math.round(dloaded + skipped + err),
|
||||
})
|
||||
}
|
||||
|
||||
if ((dloaded + skipped + err) % 100 === 0) {
|
||||
console.log("Writing views to", targetpath)
|
||||
fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
|
||||
}
|
||||
}
|
||||
console.log("Writing views to", targetpath)
|
||||
fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
|
||||
}
|
||||
|
||||
async downloadImage(url: string, imagePath: string): Promise<boolean> {
|
||||
const filenameLong = url.replace(/[\/:.\-%]/g, "_") + ".jpg"
|
||||
const targetPathLong = imagePath + "/" + filenameLong
|
||||
|
@ -390,6 +442,7 @@ export default class GenerateImageAnalysis extends Script {
|
|||
const imageBackupPath = args[0]
|
||||
await this.downloadData(datapath, cached)
|
||||
|
||||
await this.downloadViews(datapath)
|
||||
await this.downloadMetadata(datapath)
|
||||
await this.downloadAllImages(datapath, imageBackupPath)
|
||||
this.analyze(datapath)
|
||||
|
|
|
@ -27,7 +27,8 @@ function genImages(dryrun = false) {
|
|||
"star_outline",
|
||||
"star",
|
||||
"osm_logo_us",
|
||||
|
||||
"triangle",
|
||||
"teardrop_with_hole_green",
|
||||
"SocialImageForeground",
|
||||
"wikipedia",
|
||||
"Upload",
|
||||
|
|
|
@ -28,6 +28,7 @@ import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Js
|
|||
import LayerConfig from "../src/Models/ThemeConfig/LayerConfig"
|
||||
import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig"
|
||||
import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext"
|
||||
import { GenerateFavouritesLayer } from "./generateFavouritesLayer"
|
||||
|
||||
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
|
||||
// It spits out an overview of those to be used to load them
|
||||
|
@ -381,23 +382,20 @@ class LayerOverviewUtils extends Script {
|
|||
forceReload
|
||||
)
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_themes.json",
|
||||
JSON.stringify({
|
||||
themes: Array.from(sharedThemes.values()),
|
||||
})
|
||||
)
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_layers.json",
|
||||
JSON.stringify({ layers: Array.from(sharedLayers.values()) })
|
||||
JSON.stringify({
|
||||
layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"),
|
||||
})
|
||||
)
|
||||
|
||||
const mcChangesPath = "./assets/themes/mapcomplete-changes/mapcomplete-changes.json"
|
||||
if (
|
||||
(recompiledThemes.length > 0 &&
|
||||
!(recompiledThemes.length === 1 && recompiledThemes[0] === "mapcomplete-changes") &&
|
||||
args.indexOf("--generate-change-map") >= 0) ||
|
||||
!(
|
||||
recompiledThemes.length === 1 && recompiledThemes[0] === "mapcomplete-changes"
|
||||
)) ||
|
||||
args.indexOf("--generate-change-map") >= 0 ||
|
||||
!existsSync(mcChangesPath)
|
||||
) {
|
||||
// mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
|
||||
|
@ -428,6 +426,19 @@ class LayerOverviewUtils extends Script {
|
|||
ConversionContext.construct([], [])
|
||||
)
|
||||
|
||||
for (const [_, theme] of sharedThemes) {
|
||||
theme.layers = theme.layers.filter(
|
||||
(l) => Constants.added_by_default.indexOf(l["id"]) < 0
|
||||
)
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_themes.json",
|
||||
JSON.stringify({
|
||||
themes: Array.from(sharedThemes.values()),
|
||||
})
|
||||
)
|
||||
|
||||
const end = new Date()
|
||||
const millisNeeded = end.getTime() - start.getTime()
|
||||
if (AllSharedLayers.getSharedLayersConfigs().size == 0) {
|
||||
|
@ -711,7 +722,9 @@ class LayerOverviewUtils extends Script {
|
|||
ConversionContext.construct([themePath], ["PrepareLayer"])
|
||||
)
|
||||
try {
|
||||
themeFile = new PrepareTheme(convertState).convertStrict(
|
||||
themeFile = new PrepareTheme(convertState, {
|
||||
skipDefaultLayers: true,
|
||||
}).convertStrict(
|
||||
themeFile,
|
||||
ConversionContext.construct([themePath], ["PrepareLayer"])
|
||||
)
|
||||
|
@ -791,4 +804,5 @@ class LayerOverviewUtils extends Script {
|
|||
}
|
||||
}
|
||||
|
||||
new GenerateFavouritesLayer().run()
|
||||
new LayerOverviewUtils().run()
|
||||
|
|
|
@ -19,7 +19,7 @@ import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtil
|
|||
|
||||
const sharp = require("sharp")
|
||||
const template = readFileSync("theme.html", "utf8")
|
||||
const codeTemplate = readFileSync("src/index_theme.ts.template", "utf8")
|
||||
let codeTemplate = readFileSync("src/index_theme.ts.template", "utf8")
|
||||
|
||||
function enc(str: string): string {
|
||||
return encodeURIComponent(str.toLowerCase())
|
||||
|
@ -487,8 +487,19 @@ async function createIndexFor(theme: LayoutConfig) {
|
|||
`import layout from "./src/assets/generated/themes/${theme.id}.json"`,
|
||||
`import { ThemeMetaTagging } from "./src/assets/generated/metatagging/${theme.id}"`,
|
||||
]
|
||||
for (const layerName of Constants.added_by_default) {
|
||||
imports.push(`import ${layerName} from "./src/assets/generated/layers/${layerName}.json"`)
|
||||
}
|
||||
writeFileSync(filename, imports.join("\n") + "\n")
|
||||
|
||||
const addLayers = []
|
||||
|
||||
for (const layerName of Constants.added_by_default) {
|
||||
addLayers.push(` layout.layers.push(<any> ${layerName})`)
|
||||
}
|
||||
|
||||
codeTemplate = codeTemplate.replace(" // LAYOUT.ADD_LAYERS", addLayers.join("\n"))
|
||||
|
||||
appendFileSync(filename, codeTemplate)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
|||
import { Utils } from "../src/Utils"
|
||||
import { writeFileSync } from "fs"
|
||||
import ScriptUtils from "./ScriptUtils"
|
||||
import TagRenderingConfig from "../src/Models/ThemeConfig/TagRenderingConfig"
|
||||
import { And } from "../src/Logic/Tags/And"
|
||||
|
||||
/* Downloads stats on osmSource-tags and keys from tagInfo */
|
||||
|
||||
|
@ -21,7 +23,12 @@ async function main(includeTags = true) {
|
|||
continue
|
||||
}
|
||||
|
||||
const sources = TagUtils.Tag(layer.source["osmTags"])
|
||||
const sourcesList = [TagUtils.Tag(layer.source["osmTags"])]
|
||||
if (layer?.title) {
|
||||
sourcesList.push(...new TagRenderingConfig(layer.title).usedTags())
|
||||
}
|
||||
|
||||
const sources = new And(sourcesList)
|
||||
const allKeys = sources.usedKeys()
|
||||
for (const key of allKeys) {
|
||||
if (!keysAndTags.has(key)) {
|
||||
|
@ -68,6 +75,8 @@ async function main(includeTags = true) {
|
|||
"./src/assets/key_totals.json",
|
||||
JSON.stringify(
|
||||
{
|
||||
"#": "Generated with generateStats.ts",
|
||||
date: new Date().toISOString(),
|
||||
keys: Utils.MapToObj(keyTotal, (t) => t),
|
||||
tags: Utils.MapToObj(tagTotal, (v) => Utils.MapToObj(v, (t) => t)),
|
||||
},
|
||||
|
|
|
@ -44,6 +44,36 @@ async function prepareFile(url: string): Promise<string> {
|
|||
}
|
||||
return null
|
||||
}
|
||||
async function handleDelete(req: http.IncomingMessage, res: ServerResponse) {
|
||||
let body = ""
|
||||
req.on("data", (chunk) => {
|
||||
body = body + chunk
|
||||
})
|
||||
const paths = req.url.split("/")
|
||||
console.log("Got a valid delete to:", paths.join("/"))
|
||||
for (let i = 1; i < paths.length; i++) {
|
||||
const p = paths.slice(0, i)
|
||||
const dir = STATIC_PATH + p.join("/")
|
||||
if (!fs.existsSync(dir)) {
|
||||
res.writeHead(304, { "Content-Type": MIME_TYPES.html })
|
||||
res.write("<html><body>No parent directory, nothing deleted</body></html>", "utf8")
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
}
|
||||
const path = STATIC_PATH + paths.join("/")
|
||||
if(!fs.existsSync(path)){
|
||||
res.writeHead(304, { "Content-Type": MIME_TYPES.html })
|
||||
res.write("<html><body>File not found</body></html>", "utf8")
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
fs.renameSync(path, path+".bak")
|
||||
res.writeHead(200, { "Content-Type": MIME_TYPES.html })
|
||||
res.write("<html><body>File moved to backup</body></html>", "utf8")
|
||||
res.end()
|
||||
}
|
||||
|
||||
async function handlePost(req: http.IncomingMessage, res: ServerResponse) {
|
||||
let body = ""
|
||||
|
@ -53,6 +83,7 @@ async function handlePost(req: http.IncomingMessage, res: ServerResponse) {
|
|||
|
||||
await new Promise((resolve) => req.on("end", resolve))
|
||||
|
||||
console.log(new Date().toISOString())
|
||||
let parsed: any
|
||||
try {
|
||||
parsed = JSON.parse(body)
|
||||
|
@ -84,7 +115,7 @@ async function handlePost(req: http.IncomingMessage, res: ServerResponse) {
|
|||
|
||||
http.createServer(async (req: http.IncomingMessage, res) => {
|
||||
try {
|
||||
console.log(req.method + " " + req.url, "from:", req.headers.origin)
|
||||
console.log(req.method + " " + req.url, "from:", req.headers.origin, new Date().toISOString())
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept"
|
||||
|
@ -101,6 +132,12 @@ http.createServer(async (req: http.IncomingMessage, res) => {
|
|||
return
|
||||
}
|
||||
|
||||
if(req.method === "DELETE"){
|
||||
console.log("Got a DELETE", new Date())
|
||||
await handleDelete(req, res)
|
||||
return
|
||||
}
|
||||
|
||||
const url = new URL(`http://127.0.0.1/` + req.url)
|
||||
console.log("URL pathname is")
|
||||
if (url.pathname.endsWith("overview")) {
|
||||
|
|
|
@ -1,45 +1,54 @@
|
|||
import known_themes from "../assets/generated/known_themes.json"
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||
import favourite from "../assets/generated/layers/favourite.json"
|
||||
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||
import { AllSharedLayers } from "./AllSharedLayers"
|
||||
import Constants from "../Models/Constants"
|
||||
|
||||
/**
|
||||
* Somewhat of a dictionary, which lazily parses needed themes
|
||||
*/
|
||||
export class AllKnownLayoutsLazy {
|
||||
private readonly dict: Map<string, { data: LayoutConfig } | { func: () => LayoutConfig }> =
|
||||
new Map()
|
||||
constructor() {
|
||||
private readonly raw: Map<string, LayoutConfigJson> = new Map()
|
||||
private readonly dict: Map<string, LayoutConfig> = new Map()
|
||||
|
||||
constructor(includeFavouriteLayer = true) {
|
||||
for (const layoutConfigJson of known_themes["themes"]) {
|
||||
this.dict.set(layoutConfigJson.id, {
|
||||
func: () => {
|
||||
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
|
||||
for (let i = 0; i < layout.layers.length; i++) {
|
||||
let layer = layout.layers[i]
|
||||
if (typeof layer === "string") {
|
||||
throw "Layer " + layer + " was not expanded in " + layout.id
|
||||
for (const layerId of Constants.added_by_default) {
|
||||
if (layerId === "favourite" && favourite.id) {
|
||||
if (includeFavouriteLayer) {
|
||||
layoutConfigJson.layers.push(favourite)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return layout
|
||||
},
|
||||
})
|
||||
const defaultLayer = AllSharedLayers.getSharedLayersConfigs().get(layerId)
|
||||
if (defaultLayer === undefined) {
|
||||
console.error("Could not find builtin layer", layerId)
|
||||
continue
|
||||
}
|
||||
layoutConfigJson.layers.push(defaultLayer)
|
||||
}
|
||||
this.raw.set(layoutConfigJson.id, layoutConfigJson)
|
||||
}
|
||||
}
|
||||
|
||||
public getConfig(key: string): LayoutConfigJson {
|
||||
return this.raw.get(key)
|
||||
}
|
||||
|
||||
public get(key: string): LayoutConfig {
|
||||
const thunk = this.dict.get(key)
|
||||
if (thunk === undefined) {
|
||||
return undefined
|
||||
const cached = this.dict.get(key)
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
}
|
||||
if (thunk["data"]) {
|
||||
return thunk["data"]
|
||||
}
|
||||
const layout = thunk["func"]()
|
||||
this.dict.set(key, { data: layout })
|
||||
|
||||
const layout = new LayoutConfig(this.getConfig(key))
|
||||
this.dict.set(key, layout)
|
||||
return layout
|
||||
}
|
||||
|
||||
public keys() {
|
||||
return this.dict.keys()
|
||||
return this.raw.keys()
|
||||
}
|
||||
|
||||
public values() {
|
||||
|
|
|
@ -6,13 +6,22 @@ import { Changes } from "../Osm/Changes"
|
|||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import SimpleMetaTagger from "../SimpleMetaTagger"
|
||||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||
import { Feature } from "geojson"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
|
||||
import { IndexedFeatureSource } from "../FeatureSource/FeatureSource"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
interface TagsUpdaterState {
|
||||
selectedElement: UIEventSource<Feature>
|
||||
featureProperties: { getStore: (id: string) => UIEventSource<Record<string, string>> }
|
||||
changes: Changes
|
||||
osmConnection: OsmConnection
|
||||
layout: LayoutConfig
|
||||
osmObjectDownloader: OsmObjectDownloader
|
||||
indexedFeatures: IndexedFeatureSource
|
||||
}
|
||||
|
||||
export default class SelectedElementTagsUpdater {
|
||||
private static readonly metatags = new Set([
|
||||
"timestamp",
|
||||
|
@ -23,38 +32,96 @@ export default class SelectedElementTagsUpdater {
|
|||
"id",
|
||||
])
|
||||
|
||||
private readonly state: {
|
||||
selectedElement: UIEventSource<Feature>
|
||||
featureProperties: FeaturePropertiesStore
|
||||
changes: Changes
|
||||
osmConnection: OsmConnection
|
||||
layout: LayoutConfig
|
||||
osmObjectDownloader: OsmObjectDownloader
|
||||
indexedFeatures: IndexedFeatureSource
|
||||
}
|
||||
|
||||
constructor(state: {
|
||||
selectedElement: UIEventSource<Feature>
|
||||
featureProperties: FeaturePropertiesStore
|
||||
indexedFeatures: IndexedFeatureSource
|
||||
changes: Changes
|
||||
osmConnection: OsmConnection
|
||||
layout: LayoutConfig
|
||||
osmObjectDownloader: OsmObjectDownloader
|
||||
}) {
|
||||
this.state = state
|
||||
constructor(state: TagsUpdaterState) {
|
||||
state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => {
|
||||
if (!isLoggedIn && !Utils.runningFromConsole) {
|
||||
return
|
||||
}
|
||||
this.installCallback()
|
||||
this.installCallback(state)
|
||||
// We only have to do this once...
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
private installCallback() {
|
||||
const state = this.state
|
||||
public static applyUpdate(latestTags: OsmTags, id: string, state: TagsUpdaterState) {
|
||||
try {
|
||||
const leftRightSensitive = state.layout.isLeftRightSensitive()
|
||||
|
||||
if (leftRightSensitive) {
|
||||
SimpleMetaTagger.removeBothTagging(latestTags)
|
||||
}
|
||||
|
||||
const pendingChanges = state.changes.pendingChanges.data
|
||||
.filter((change) => change.type + "/" + change.id === id)
|
||||
.filter((change) => change.tags !== undefined)
|
||||
|
||||
for (const pendingChange of pendingChanges) {
|
||||
const tagChanges = pendingChange.tags
|
||||
for (const tagChange of tagChanges) {
|
||||
const key = tagChange.k
|
||||
const v = tagChange.v
|
||||
if (v === undefined || v === "") {
|
||||
delete latestTags[key]
|
||||
} else {
|
||||
latestTags[key] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With the changes applied, we merge them onto the upstream object
|
||||
let somethingChanged = false
|
||||
const currentTagsSource = state.featureProperties.getStore(id)
|
||||
if (currentTagsSource === undefined) {
|
||||
console.warn("No tags store found for", id, "cannot update tags")
|
||||
return
|
||||
}
|
||||
const currentTags = currentTagsSource.data
|
||||
for (const key in latestTags) {
|
||||
let osmValue = latestTags[key]
|
||||
|
||||
if (typeof osmValue === "number") {
|
||||
osmValue = "" + osmValue
|
||||
}
|
||||
|
||||
const localValue = currentTags[key]
|
||||
if (localValue !== osmValue) {
|
||||
somethingChanged = true
|
||||
currentTags[key] = osmValue
|
||||
}
|
||||
}
|
||||
|
||||
for (const currentKey in currentTags) {
|
||||
if (currentKey.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
if (SelectedElementTagsUpdater.metatags.has(currentKey)) {
|
||||
continue
|
||||
}
|
||||
if (currentKey in latestTags) {
|
||||
continue
|
||||
}
|
||||
console.log("Removing key as deleted upstream", currentKey)
|
||||
delete currentTags[currentKey]
|
||||
somethingChanged = true
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
console.log(
|
||||
"Detected upstream changes to the object " +
|
||||
id +
|
||||
" when opening it, updating..."
|
||||
)
|
||||
currentTagsSource.ping()
|
||||
} else {
|
||||
console.debug("Fetched latest tags for ", id, "but detected no changes")
|
||||
}
|
||||
return currentTags
|
||||
} catch (e) {
|
||||
console.error("Updating the tags of selected element ", id, "failed due to", e)
|
||||
}
|
||||
}
|
||||
|
||||
private installCallback(state: TagsUpdaterState) {
|
||||
state.selectedElement.addCallbackAndRunD(async (s) => {
|
||||
let id = s.properties?.id
|
||||
if (!id) {
|
||||
|
@ -94,7 +161,7 @@ export default class SelectedElementTagsUpdater {
|
|||
oldFeature.geometry = newGeometry
|
||||
state.featureProperties.getStore(id)?.ping()
|
||||
}
|
||||
this.applyUpdate(latestTags, id)
|
||||
SelectedElementTagsUpdater.applyUpdate(latestTags, id, state)
|
||||
|
||||
console.log("Updated", id)
|
||||
} catch (e) {
|
||||
|
@ -102,73 +169,4 @@ export default class SelectedElementTagsUpdater {
|
|||
}
|
||||
})
|
||||
}
|
||||
private applyUpdate(latestTags: OsmTags, id: string) {
|
||||
const state = this.state
|
||||
try {
|
||||
const leftRightSensitive = state.layout.isLeftRightSensitive()
|
||||
|
||||
if (leftRightSensitive) {
|
||||
SimpleMetaTagger.removeBothTagging(latestTags)
|
||||
}
|
||||
|
||||
const pendingChanges = state.changes.pendingChanges.data
|
||||
.filter((change) => change.type + "/" + change.id === id)
|
||||
.filter((change) => change.tags !== undefined)
|
||||
|
||||
for (const pendingChange of pendingChanges) {
|
||||
const tagChanges = pendingChange.tags
|
||||
for (const tagChange of tagChanges) {
|
||||
const key = tagChange.k
|
||||
const v = tagChange.v
|
||||
if (v === undefined || v === "") {
|
||||
delete latestTags[key]
|
||||
} else {
|
||||
latestTags[key] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With the changes applied, we merge them onto the upstream object
|
||||
let somethingChanged = false
|
||||
const currentTagsSource = state.featureProperties.getStore(id)
|
||||
const currentTags = currentTagsSource.data
|
||||
for (const key in latestTags) {
|
||||
let osmValue = latestTags[key]
|
||||
|
||||
if (typeof osmValue === "number") {
|
||||
osmValue = "" + osmValue
|
||||
}
|
||||
|
||||
const localValue = currentTags[key]
|
||||
if (localValue !== osmValue) {
|
||||
somethingChanged = true
|
||||
currentTags[key] = osmValue
|
||||
}
|
||||
}
|
||||
|
||||
for (const currentKey in currentTags) {
|
||||
if (currentKey.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
if (SelectedElementTagsUpdater.metatags.has(currentKey)) {
|
||||
continue
|
||||
}
|
||||
if (currentKey in latestTags) {
|
||||
continue
|
||||
}
|
||||
console.log("Removing key as deleted upstream", currentKey)
|
||||
delete currentTags[currentKey]
|
||||
somethingChanged = true
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
console.log("Detected upstream changes to the object when opening it, updating...")
|
||||
currentTagsSource.ping()
|
||||
} else {
|
||||
console.debug("Fetched latest tags for ", id, "but detected no changes")
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Updating the tags of selected element ", id, "failed due to", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,9 +139,9 @@ export default class DetermineLayout {
|
|||
const layerConfig = <LayerConfigJson>json
|
||||
const iconTr: string | TagRenderingConfigJson = <any>(
|
||||
layerConfig.pointRendering
|
||||
.map((mr) => mr.marker.find((icon) => icon.icon !== undefined).icon)
|
||||
.map((mr) => mr?.marker?.find((icon) => icon.icon !== undefined)?.icon)
|
||||
.find((i) => i !== undefined)
|
||||
)
|
||||
) ?? "bug"
|
||||
const icon = new TagRenderingConfig(iconTr).render.txt
|
||||
json = {
|
||||
id: json.id,
|
||||
|
|
220
src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts
Normal file
|
@ -0,0 +1,220 @@
|
|||
import StaticFeatureSource from "./StaticFeatureSource"
|
||||
import { Feature } from "geojson"
|
||||
import { Store, Stores, UIEventSource } from "../../UIEventSource"
|
||||
import { OsmConnection } from "../../Osm/OsmConnection"
|
||||
import { OsmId } from "../../../Models/OsmFeature"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import { IndexedFeatureSource } from "../FeatureSource"
|
||||
import OsmObjectDownloader from "../../Osm/OsmObjectDownloader"
|
||||
import { SpecialVisualizationState } from "../../../UI/SpecialVisualization"
|
||||
import SelectedElementTagsUpdater from "../../Actors/SelectedElementTagsUpdater"
|
||||
|
||||
/**
|
||||
* Generates the favourites from the preferences and marks them as favourite
|
||||
*/
|
||||
export default class FavouritesFeatureSource extends StaticFeatureSource {
|
||||
public static readonly prefix = "mapcomplete-favourite-"
|
||||
private readonly _osmConnection: OsmConnection
|
||||
private readonly _detectedIds: Store<string[]>
|
||||
|
||||
/**
|
||||
* All favourites, including the ones which are filtered away because they are already displayed
|
||||
*/
|
||||
public readonly allFavourites: Store<Feature[]>
|
||||
|
||||
constructor(state: SpecialVisualizationState) {
|
||||
const features: Store<Feature[]> = Stores.ListStabilized(
|
||||
state.osmConnection.preferencesHandler.preferences.map((prefs) => {
|
||||
const feats: Feature[] = []
|
||||
const allIds = new Set<string>()
|
||||
for (const key in prefs) {
|
||||
if (!key.startsWith(FavouritesFeatureSource.prefix)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const feat = FavouritesFeatureSource.ExtractFavourite(key, prefs)
|
||||
if (!feat) {
|
||||
continue
|
||||
}
|
||||
feats.push(feat)
|
||||
allIds.add(feat.properties.id)
|
||||
} catch (e) {
|
||||
console.error("Could not create favourite from", key, "due to", e)
|
||||
}
|
||||
}
|
||||
return feats
|
||||
})
|
||||
)
|
||||
|
||||
const featuresWithoutAlreadyPresent = features.map((features) =>
|
||||
features.filter(
|
||||
(feat) => !state.layout.layers.some((l) => l.id === feat.properties._orig_layer)
|
||||
)
|
||||
)
|
||||
|
||||
super(featuresWithoutAlreadyPresent)
|
||||
this.allFavourites = features
|
||||
|
||||
this._osmConnection = state.osmConnection
|
||||
this._detectedIds = Stores.ListStabilized(
|
||||
features.map((feats) => feats.map((f) => f.properties.id))
|
||||
)
|
||||
let allFeatures = state.indexedFeatures
|
||||
this._detectedIds.addCallbackAndRunD((detected) =>
|
||||
this.markFeatures(detected, state.featureProperties, allFeatures)
|
||||
)
|
||||
// We use the indexedFeatureSource as signal to update
|
||||
allFeatures.features.map((_) =>
|
||||
this.markFeatures(this._detectedIds.data, state.featureProperties, allFeatures)
|
||||
)
|
||||
|
||||
this.allFavourites.addCallbackD((features) => {
|
||||
for (const feature of features) {
|
||||
this.updateFeature(feature, state.osmObjectDownloader, state)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
private async updateFeature(
|
||||
feature: Feature,
|
||||
osmObjectDownloader: OsmObjectDownloader,
|
||||
state: SpecialVisualizationState
|
||||
) {
|
||||
const id = feature.properties.id
|
||||
const upstream = await osmObjectDownloader.DownloadObjectAsync(id)
|
||||
if (upstream === "deleted") {
|
||||
this.removeFavourite(feature)
|
||||
return
|
||||
}
|
||||
console.log("Updating metadata due to favourite of", id)
|
||||
const latestTags = SelectedElementTagsUpdater.applyUpdate(upstream.tags, id, state)
|
||||
this.updatePropertiesOfFavourite(latestTags)
|
||||
}
|
||||
|
||||
private static ExtractFavourite(key: string, prefs: Record<string, string>): Feature {
|
||||
const id = key.substring(FavouritesFeatureSource.prefix.length)
|
||||
const osmId = id.replace("-", "/")
|
||||
if (id.indexOf("-property-") > 0 || id.endsWith("-layer") || id.endsWith("-theme")) {
|
||||
return undefined
|
||||
}
|
||||
const geometry = <[number, number]>JSON.parse(prefs[key])
|
||||
const properties = FavouritesFeatureSource.getPropertiesFor(prefs, id)
|
||||
properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"]
|
||||
properties._orig_theme = prefs[FavouritesFeatureSource.prefix + id + "-theme"]
|
||||
|
||||
properties.id = osmId
|
||||
properties._favourite = "yes"
|
||||
return {
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: geometry,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
private static getPropertiesFor(
|
||||
prefs: Record<string, string>,
|
||||
id: string
|
||||
): Record<string, string> {
|
||||
const properties: Record<string, string> = {}
|
||||
const minLength = FavouritesFeatureSource.prefix.length + id.length + "-property-".length
|
||||
for (const key in prefs) {
|
||||
if (key.length < minLength) {
|
||||
continue
|
||||
}
|
||||
if (!key.startsWith(FavouritesFeatureSource.prefix + id)) {
|
||||
continue
|
||||
}
|
||||
const propertyName = key.substring(minLength).replaceAll("__", ":")
|
||||
properties[propertyName] = prefs[key]
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all the (normal) properties as the feature is updated
|
||||
*/
|
||||
private updatePropertiesOfFavourite(properties: Record<string, string>) {
|
||||
const id = properties?.id?.replace("/", "-")
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
console.log("Updating store for", id)
|
||||
for (const key in properties) {
|
||||
const pref = this._osmConnection.GetPreference(
|
||||
"favourite-" + id + "-property-" + key.replaceAll(":", "__")
|
||||
)
|
||||
const v = properties[key]
|
||||
if (v === "" || !v) {
|
||||
continue
|
||||
}
|
||||
pref.setData("" + v)
|
||||
}
|
||||
}
|
||||
|
||||
public removeFavourite(feature: Feature, tags?: UIEventSource<Record<string, string>>) {
|
||||
const id = feature.properties.id.replace("/", "-")
|
||||
const pref = this._osmConnection.GetPreference("favourite-" + id)
|
||||
this._osmConnection.preferencesHandler.removeAllWithPrefix("mapcomplete-favourite-" + id)
|
||||
if (tags) {
|
||||
delete tags.data._favourite
|
||||
tags.ping()
|
||||
}
|
||||
}
|
||||
|
||||
public markAsFavourite(
|
||||
feature: Feature,
|
||||
layer: string,
|
||||
theme: string,
|
||||
tags: UIEventSource<Record<string, string> & { id: OsmId }>,
|
||||
isFavourite: boolean = true
|
||||
) {
|
||||
{
|
||||
if (!isFavourite) {
|
||||
this.removeFavourite(feature, tags)
|
||||
return
|
||||
}
|
||||
const id = tags.data.id.replace("/", "-")
|
||||
const pref = this._osmConnection.GetPreference("favourite-" + id)
|
||||
const center = GeoOperations.centerpointCoordinates(feature)
|
||||
pref.setData(JSON.stringify(center))
|
||||
this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer)
|
||||
this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme)
|
||||
this.updatePropertiesOfFavourite(tags.data)
|
||||
}
|
||||
tags.data._favourite = "yes"
|
||||
tags.ping()
|
||||
}
|
||||
|
||||
private markFeatures(
|
||||
detected: string[],
|
||||
featureProperties: { getStore(id: string): UIEventSource<Record<string, string>> },
|
||||
allFeatures: IndexedFeatureSource
|
||||
) {
|
||||
const feature = allFeatures.features.data
|
||||
for (const f of feature) {
|
||||
const id = f.properties.id
|
||||
if (!id) {
|
||||
continue
|
||||
}
|
||||
const store = featureProperties.getStore(id)
|
||||
const origValue = store.data._favourite
|
||||
if (detected.indexOf(id) >= 0) {
|
||||
if (origValue !== "yes") {
|
||||
store.data._favourite = "yes"
|
||||
store.ping()
|
||||
}
|
||||
} else {
|
||||
if (origValue) {
|
||||
store.data._favourite = ""
|
||||
store.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,14 @@ import FilteringFeatureSource from "./FilteringFeatureSource"
|
|||
import LayerState from "../../State/LayerState"
|
||||
|
||||
export default class NearbyFeatureSource implements FeatureSource {
|
||||
private readonly _result = new UIEventSource<Feature[]>(undefined)
|
||||
|
||||
public readonly features: Store<Feature[]>
|
||||
private readonly _targetPoint: Store<{ lon: number; lat: number }>
|
||||
private readonly _numberOfNeededFeatures: number
|
||||
private readonly _layerState?: LayerState
|
||||
private readonly _currentZoom: Store<number>
|
||||
private readonly _allSources: Store<{ feat: Feature; d: number }[]>[] = []
|
||||
|
||||
constructor(
|
||||
targetPoint: Store<{ lon: number; lat: number }>,
|
||||
|
@ -18,41 +22,44 @@ export default class NearbyFeatureSource implements FeatureSource {
|
|||
layerState?: LayerState,
|
||||
currentZoom?: Store<number>
|
||||
) {
|
||||
this._layerState = layerState
|
||||
this._targetPoint = targetPoint.stabilized(100)
|
||||
this._numberOfNeededFeatures = numberOfNeededFeatures
|
||||
this._currentZoom = currentZoom.stabilized(500)
|
||||
|
||||
const allSources: Store<{ feat: Feature; d: number }[]>[] = []
|
||||
let minzoom = 999
|
||||
|
||||
const result = new UIEventSource<Feature[]>(undefined)
|
||||
this.features = Stores.ListStabilized(result)
|
||||
|
||||
function update() {
|
||||
let features: { feat: Feature; d: number }[] = []
|
||||
for (const src of allSources) {
|
||||
features.push(...src.data)
|
||||
}
|
||||
features.sort((a, b) => a.d - b.d)
|
||||
if (numberOfNeededFeatures !== undefined) {
|
||||
features = features.slice(0, numberOfNeededFeatures)
|
||||
}
|
||||
result.setData(features.map((f) => f.feat))
|
||||
}
|
||||
this.features = Stores.ListStabilized(this._result)
|
||||
|
||||
sources.forEach((source, layer) => {
|
||||
const flayer = layerState?.filteredLayers.get(layer)
|
||||
minzoom = Math.min(minzoom, flayer.layerDef.minzoom)
|
||||
this.registerSource(source, layer)
|
||||
})
|
||||
}
|
||||
|
||||
public registerSource(source: FeatureSource, layerId: string) {
|
||||
const flayer = this._layerState?.filteredLayers.get(layerId)
|
||||
if (!flayer) {
|
||||
return
|
||||
}
|
||||
const calcSource = this.createSource(
|
||||
source.features,
|
||||
flayer.layerDef.minzoom,
|
||||
flayer.isDisplayed
|
||||
)
|
||||
calcSource.addCallbackAndRunD((features) => {
|
||||
update()
|
||||
})
|
||||
allSources.push(calcSource)
|
||||
this.update()
|
||||
})
|
||||
this._allSources.push(calcSource)
|
||||
}
|
||||
|
||||
private update() {
|
||||
let features: { feat: Feature; d: number }[] = []
|
||||
for (const src of this._allSources) {
|
||||
features.push(...src.data)
|
||||
}
|
||||
features.sort((a, b) => a.d - b.d)
|
||||
if (this._numberOfNeededFeatures !== undefined) {
|
||||
features = features.slice(0, this._numberOfNeededFeatures)
|
||||
}
|
||||
this._result.setData(features.map((f) => f.feat))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -501,147 +501,43 @@ export class GeoOperations {
|
|||
)
|
||||
}
|
||||
|
||||
public static IdentifieCommonSegments(coordinatess: [number, number][][]): {
|
||||
originalIndex: number
|
||||
segmentShardWith: number[]
|
||||
coordinates: []
|
||||
}[] {
|
||||
// An edge. Note that the edge might be reversed to fix the sorting condition: start[0] < end[0] && (start[0] != end[0] || start[0] < end[1])
|
||||
type edge = {
|
||||
start: [number, number]
|
||||
end: [number, number]
|
||||
intermediate: [number, number][]
|
||||
members: { index: number; isReversed: boolean }[]
|
||||
/**
|
||||
* Given a list of points, convert into a GPX-list, e.g. for favourites
|
||||
* @param locations
|
||||
* @param title
|
||||
*/
|
||||
public static toGpxPoints(
|
||||
locations: Feature<Point, { date?: string; altitude?: number | string }>[],
|
||||
title?: string
|
||||
) {
|
||||
title = title?.trim()
|
||||
if (title === undefined || title === "") {
|
||||
title = "Created with MapComplete"
|
||||
}
|
||||
|
||||
// The strategy:
|
||||
// 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them
|
||||
// 2. Join these edges back together - as long as their membership groups are the same
|
||||
// 3. Convert to results
|
||||
|
||||
const allEdgesByKey = new Map<string, edge>()
|
||||
|
||||
for (let index = 0; index < coordinatess.length; index++) {
|
||||
const coordinates = coordinatess[index]
|
||||
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||
const c0 = coordinates[i]
|
||||
const c1 = coordinates[i + 1]
|
||||
const isReversed = c0[0] > c1[0] || (c0[0] == c1[0] && c0[1] > c1[1])
|
||||
|
||||
let key: string
|
||||
if (isReversed) {
|
||||
key = "" + c1 + ";" + c0
|
||||
} else {
|
||||
key = "" + c0 + ";" + c1
|
||||
}
|
||||
const member = { index, isReversed }
|
||||
if (allEdgesByKey.has(key)) {
|
||||
allEdgesByKey.get(key).members.push(member)
|
||||
continue
|
||||
}
|
||||
|
||||
let edge: edge
|
||||
if (!isReversed) {
|
||||
edge = {
|
||||
start: c0,
|
||||
end: c1,
|
||||
members: [member],
|
||||
intermediate: [],
|
||||
}
|
||||
} else {
|
||||
edge = {
|
||||
start: c1,
|
||||
end: c0,
|
||||
members: [member],
|
||||
intermediate: [],
|
||||
title = Utils.EncodeXmlValue(title)
|
||||
const trackPoints: string[] = []
|
||||
for (const l of locations) {
|
||||
let trkpt = ` <wpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">`
|
||||
for (const key in l.properties) {
|
||||
const keyCleaned = key.replaceAll(":", "__")
|
||||
trkpt += ` <${keyCleaned}>${l.properties[key]}</${keyCleaned}>\n`
|
||||
if (key === "website") {
|
||||
trkpt += ` <link>${l.properties[key]}</link>\n`
|
||||
}
|
||||
}
|
||||
allEdgesByKey.set(key, edge)
|
||||
trkpt += " </wpt>\n"
|
||||
trackPoints.push(trkpt)
|
||||
}
|
||||
}
|
||||
|
||||
// Lets merge them back together!
|
||||
|
||||
let didMergeSomething = false
|
||||
let allMergedEdges = Array.from(allEdgesByKey.values())
|
||||
const allEdgesByStartPoint = new Map<string, edge[]>()
|
||||
for (const edge of allMergedEdges) {
|
||||
edge.members.sort((m0, m1) => m0.index - m1.index)
|
||||
|
||||
const kstart = edge.start + ""
|
||||
if (!allEdgesByStartPoint.has(kstart)) {
|
||||
allEdgesByStartPoint.set(kstart, [])
|
||||
}
|
||||
allEdgesByStartPoint.get(kstart).push(edge)
|
||||
}
|
||||
|
||||
function membersAreCompatible(first: edge, second: edge): boolean {
|
||||
// There must be an exact match between the members
|
||||
if (first.members === second.members) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (first.members.length !== second.members.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Members are sorted and have the same length, so we can check quickly
|
||||
for (let i = 0; i < first.members.length; i++) {
|
||||
const m0 = first.members[i]
|
||||
const m1 = second.members[i]
|
||||
if (m0.index !== m1.index || m0.isReversed !== m1.isReversed) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Allrigth, they are the same, lets mark this permanently
|
||||
second.members = first.members
|
||||
return true
|
||||
}
|
||||
|
||||
do {
|
||||
didMergeSomething = false
|
||||
// We use 'allMergedEdges' as our running list
|
||||
const consumed = new Set<edge>()
|
||||
for (const edge of allMergedEdges) {
|
||||
// Can we make this edge longer at the end?
|
||||
if (consumed.has(edge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
console.log("Considering edge", edge)
|
||||
const matchingEndEdges = allEdgesByStartPoint.get(edge.end + "")
|
||||
console.log("Matchign endpoints:", matchingEndEdges)
|
||||
if (matchingEndEdges === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (let i = 0; i < matchingEndEdges.length; i++) {
|
||||
const endEdge = matchingEndEdges[i]
|
||||
|
||||
if (consumed.has(endEdge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!membersAreCompatible(edge, endEdge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We can make the segment longer!
|
||||
didMergeSomething = true
|
||||
console.log("Merging ", edge, "with ", endEdge)
|
||||
edge.intermediate.push(edge.end)
|
||||
edge.end = endEdge.end
|
||||
consumed.add(endEdge)
|
||||
matchingEndEdges.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
allMergedEdges = allMergedEdges.filter((edge) => !consumed.has(edge))
|
||||
} while (didMergeSomething)
|
||||
|
||||
return []
|
||||
const header =
|
||||
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
|
||||
return (
|
||||
header +
|
||||
"\n<name>" +
|
||||
title +
|
||||
"</name>\n<trk><trkseg>\n" +
|
||||
trackPoints.join("\n") +
|
||||
"\n</trkseg></trk></gpx>"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,11 +31,12 @@ export default class GenericImageProvider extends ImageProvider {
|
|||
key: key,
|
||||
url: value,
|
||||
provider: this,
|
||||
id: value
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
SourceIcon(backlinkSource?: string) {
|
||||
SourceIcon() {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@ import { Utils } from "../../Utils"
|
|||
|
||||
export interface ProvidedImage {
|
||||
url: string
|
||||
url_hd?: string
|
||||
key: string
|
||||
provider: ImageProvider
|
||||
id: string
|
||||
}
|
||||
|
||||
export default abstract class ImageProvider {
|
||||
public abstract readonly defaultKeyPrefixes: string[]
|
||||
|
||||
public abstract SourceIcon(backlinkSource?: string): BaseUIElement
|
||||
public abstract SourceIcon(id?: string, location?: { lon: number; lat: number }): BaseUIElement
|
||||
|
||||
/**
|
||||
* Given a properies object, maps it onto _all_ the available pictures for this imageProvider
|
||||
|
@ -28,7 +30,7 @@ export default abstract class ImageProvider {
|
|||
throw "No `defaultKeyPrefixes` defined by this image provider"
|
||||
}
|
||||
const relevantUrls = new UIEventSource<
|
||||
{ url: string; key: string; provider: ImageProvider }[]
|
||||
{ id: string; url: string; key: string; provider: ImageProvider }[]
|
||||
>([])
|
||||
const seenValues = new Set<string>()
|
||||
allTags.addCallbackAndRunD((tags) => {
|
||||
|
@ -67,4 +69,8 @@ export default abstract class ImageProvider {
|
|||
public abstract DownloadAttribution(url: string): Promise<LicenseInfo>
|
||||
|
||||
public abstract apiUrls(): string[]
|
||||
|
||||
public backlink(): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,8 @@ export class ImageUploadManager {
|
|||
title,
|
||||
description,
|
||||
file,
|
||||
targetKey
|
||||
targetKey,
|
||||
tags?.data?.["_orig_theme"]
|
||||
)
|
||||
if (!isNaN(Number(featureId))) {
|
||||
// This is a map note
|
||||
|
@ -126,7 +127,8 @@ export class ImageUploadManager {
|
|||
title: string,
|
||||
description: string,
|
||||
blob: File,
|
||||
targetKey: string | undefined
|
||||
targetKey: string | undefined,
|
||||
theme?: string
|
||||
): Promise<LinkImageAction> {
|
||||
this.increaseCountFor(this._uploadStarted, featureId)
|
||||
const properties = this._featureProperties.getStore(featureId)
|
||||
|
@ -148,7 +150,7 @@ export class ImageUploadManager {
|
|||
console.log("Uploading done, creating action for", featureId)
|
||||
key = targetKey ?? key
|
||||
const action = new LinkImageAction(featureId, key, value, properties, {
|
||||
theme: this._layout.id,
|
||||
theme: theme ?? this._layout.id,
|
||||
changeType: "add-image",
|
||||
})
|
||||
this.increaseCountFor(this._uploadFinished, featureId)
|
||||
|
|
|
@ -66,6 +66,7 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
url: value,
|
||||
key: key,
|
||||
provider: this,
|
||||
id: value
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
@ -81,6 +82,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
* const expected = new LicenseInfo()
|
||||
* expected.licenseShortName = "CC-BY 4.0"
|
||||
* expected.artist = "Pieter Vander Vennet"
|
||||
* expected.date = new Date(1655052078000)
|
||||
* expected.views = 2
|
||||
* licenseInfo // => expected
|
||||
*/
|
||||
public async DownloadAttribution(url: string): Promise<LicenseInfo> {
|
||||
|
@ -93,6 +96,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
|
||||
const descr: string = response.data.description ?? ""
|
||||
const data: any = {}
|
||||
const imgurData = response.data
|
||||
|
||||
for (const tag of descr.split("\n")) {
|
||||
const kv = tag.split(":")
|
||||
const k = kv[0]
|
||||
|
@ -103,6 +108,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
|
||||
licenseInfo.licenseShortName = data.license
|
||||
licenseInfo.artist = data.author
|
||||
licenseInfo.date = new Date(Number(imgurData.datetime) * 1000)
|
||||
licenseInfo.views = imgurData.views
|
||||
|
||||
return licenseInfo
|
||||
}
|
||||
|
|
|
@ -9,4 +9,6 @@ export class LicenseInfo {
|
|||
credit: string = ""
|
||||
description: string = ""
|
||||
informationLocation: URL = undefined
|
||||
date?: Date
|
||||
views?: number
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import Svg from "../../Svg"
|
|||
import { Utils } from "../../Utils"
|
||||
import { LicenseInfo } from "./LicenseInfo"
|
||||
import Constants from "../../Models/Constants"
|
||||
import Link from "../../UI/Base/Link"
|
||||
|
||||
export class Mapillary extends ImageProvider {
|
||||
public static readonly singleton = new Mapillary()
|
||||
|
@ -17,10 +18,6 @@ export class Mapillary extends ImageProvider {
|
|||
]
|
||||
defaultKeyPrefixes = ["mapillary", "image"]
|
||||
|
||||
apiUrls(): string[] {
|
||||
return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this is the same URL
|
||||
* Ignores 'stp' parameter
|
||||
|
@ -57,6 +54,30 @@ export class Mapillary extends ImageProvider {
|
|||
return false
|
||||
}
|
||||
|
||||
static createLink(
|
||||
location: {
|
||||
lon: number
|
||||
lat: number
|
||||
} = undefined,
|
||||
zoom: number = 17,
|
||||
pKey?: string
|
||||
) {
|
||||
const params = {
|
||||
focus: pKey === undefined ? "map" : "photo",
|
||||
lat: location?.lat,
|
||||
lng: location?.lon,
|
||||
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||
pKey,
|
||||
}
|
||||
const baselink = `https://www.mapillary.com/app/?`
|
||||
const paramsStr = Utils.NoNull(
|
||||
Object.keys(params).map((k) =>
|
||||
params[k] === undefined ? undefined : k + "=" + params[k]
|
||||
)
|
||||
)
|
||||
return baselink + paramsStr.join("&")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct key for API v4.0
|
||||
*/
|
||||
|
@ -80,8 +101,22 @@ export class Mapillary extends ImageProvider {
|
|||
return undefined
|
||||
}
|
||||
|
||||
SourceIcon(backlinkSource?: string): BaseUIElement {
|
||||
return Svg.mapillary_svg()
|
||||
apiUrls(): string[] {
|
||||
return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
|
||||
}
|
||||
|
||||
SourceIcon(
|
||||
id: string,
|
||||
location?: {
|
||||
lon: number
|
||||
lat: number
|
||||
}
|
||||
): BaseUIElement {
|
||||
const icon = Svg.mapillary_svg()
|
||||
if (!id) {
|
||||
return icon
|
||||
}
|
||||
return new Link(icon, Mapillary.createLink(location, 16, "" + id), true)
|
||||
}
|
||||
|
||||
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
|
||||
|
@ -106,14 +141,18 @@ export class Mapillary extends ImageProvider {
|
|||
const metadataUrl =
|
||||
"https://graph.mapillary.com/" +
|
||||
mapillaryId +
|
||||
"?fields=thumb_1024_url&&access_token=" +
|
||||
"?fields=thumb_1024_url,thumb_original_url&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached(metadataUrl, 60 * 60)
|
||||
const url = <string>response["thumb_1024_url"]
|
||||
console.log(response)
|
||||
const url_hd = <string>response["thumb_original_url"]
|
||||
return {
|
||||
url: url,
|
||||
id: "" + mapillaryId,
|
||||
url,
|
||||
url_hd,
|
||||
provider: this,
|
||||
key: key,
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export class WikidataImageProvider extends ImageProvider {
|
|||
super()
|
||||
}
|
||||
|
||||
public SourceIcon(_?: string): BaseUIElement {
|
||||
public SourceIcon(): BaseUIElement {
|
||||
return Svg.wikidata_svg()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import ImageProvider, { ProvidedImage } from "./ImageProvider"
|
||||
import BaseUIElement from "../../UI/BaseUIElement"
|
||||
import Svg from "../../Svg"
|
||||
import Link from "../../UI/Base/Link"
|
||||
import { Utils } from "../../Utils"
|
||||
import { LicenseInfo } from "./LicenseInfo"
|
||||
import Wikimedia from "../Web/Wikimedia"
|
||||
|
@ -70,17 +69,8 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
return WikimediaImageProvider.apiUrls
|
||||
}
|
||||
|
||||
SourceIcon(backlink: string): BaseUIElement {
|
||||
const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
|
||||
if (backlink === undefined) {
|
||||
return img
|
||||
}
|
||||
|
||||
return new Link(
|
||||
Svg.wikimedia_commons_white_svg(),
|
||||
`https://commons.wikimedia.org/wiki/${backlink}`,
|
||||
true
|
||||
)
|
||||
SourceIcon(): BaseUIElement {
|
||||
return Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
|
||||
}
|
||||
|
||||
public PrepUrl(value: string): ProvidedImage {
|
||||
|
@ -173,6 +163,6 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
if (!image.startsWith("File:")) {
|
||||
image = "File:" + image
|
||||
}
|
||||
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this }
|
||||
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this , id: image}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ export class OsmPreferences {
|
|||
"all-osm-preferences",
|
||||
{}
|
||||
)
|
||||
/**
|
||||
* A map containing the individual preference sources
|
||||
* @private
|
||||
*/
|
||||
private readonly preferenceSources = new Map<string, UIEventSource<string>>()
|
||||
private auth: any
|
||||
private userDetails: UIEventSource<UserDetails>
|
||||
|
@ -21,7 +25,10 @@ export class OsmPreferences {
|
|||
this.auth = auth
|
||||
this.userDetails = osmConnection.userDetails
|
||||
const self = this
|
||||
osmConnection.OnLoggedIn(() => self.UpdatePreferences())
|
||||
osmConnection.OnLoggedIn(() => {
|
||||
self.UpdatePreferences(true)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,11 +79,19 @@ export class OsmPreferences {
|
|||
let i = 0
|
||||
while (str !== "") {
|
||||
if (str === undefined || str === "undefined") {
|
||||
source.setData(undefined)
|
||||
throw (
|
||||
"Got 'undefined' or a literal string containing 'undefined' for a long preference with name " +
|
||||
key
|
||||
)
|
||||
}
|
||||
if (str === "undefined") {
|
||||
source.setData(undefined)
|
||||
throw (
|
||||
"Got a literal string containing 'undefined' for a long preference with name " +
|
||||
key
|
||||
)
|
||||
}
|
||||
if (i > 100) {
|
||||
throw "This long preference is getting very long... "
|
||||
}
|
||||
|
@ -197,7 +212,7 @@ export class OsmPreferences {
|
|||
})
|
||||
}
|
||||
|
||||
private UpdatePreferences() {
|
||||
private UpdatePreferences(forceUpdate?: boolean) {
|
||||
const self = this
|
||||
this.auth.xhr(
|
||||
{
|
||||
|
@ -210,11 +225,22 @@ export class OsmPreferences {
|
|||
return
|
||||
}
|
||||
const prefs = value.getElementsByTagName("preference")
|
||||
const seenKeys = new Set<string>()
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
const pref = prefs[i]
|
||||
const k = pref.getAttribute("k")
|
||||
const v = pref.getAttribute("v")
|
||||
self.preferences.data[k] = v
|
||||
seenKeys.add(k)
|
||||
}
|
||||
if (forceUpdate) {
|
||||
for (let key in self.preferences.data) {
|
||||
if (seenKeys.has(key)) {
|
||||
continue
|
||||
}
|
||||
console.log("Deleting key", key, "as we didn't find it upstream")
|
||||
delete self.preferences.data[key]
|
||||
}
|
||||
}
|
||||
|
||||
// We merge all the preferences: new keys are uploaded
|
||||
|
@ -285,4 +311,14 @@ export class OsmPreferences {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
removeAllWithPrefix(prefix: string) {
|
||||
for (const key in this.preferences.data) {
|
||||
if (key.startsWith(prefix)) {
|
||||
this.GetPreference(key, "", { prefix: "" }).setData(undefined)
|
||||
console.log("Clearing preference", key)
|
||||
}
|
||||
}
|
||||
this.preferences.ping()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class UserRelatedState {
|
|||
public readonly installedUserThemes: Store<string[]>
|
||||
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
||||
public readonly showCrosshair: UIEventSource<"yes" | undefined>
|
||||
public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined>
|
||||
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
||||
public readonly homeLocation: FeatureSource
|
||||
/**
|
||||
|
@ -294,6 +294,9 @@ export default class UserRelatedState {
|
|||
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
|
||||
for (const k in newPrefs) {
|
||||
const v = newPrefs[k]
|
||||
if (v === "undefined" || !v) {
|
||||
continue
|
||||
}
|
||||
if (k.endsWith("-combined-length")) {
|
||||
const l = Number(v)
|
||||
const key = k.substring(0, k.length - "length".length)
|
||||
|
@ -308,7 +311,6 @@ export default class UserRelatedState {
|
|||
}
|
||||
|
||||
amendedPrefs.ping()
|
||||
console.log("Amended prefs are:", amendedPrefs.data)
|
||||
})
|
||||
const translationMode = osmConnection.GetPreference("translation-mode")
|
||||
|
||||
|
|
|
@ -4,39 +4,11 @@ export class ThemeMetaTagging {
|
|||
public static readonly themeName = "usersettings"
|
||||
|
||||
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||
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"
|
||||
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'
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import { Or } from "./Or"
|
|||
import { TagUtils } from "./TagUtils"
|
||||
import { Tag } from "./Tag"
|
||||
import { RegexTag } from "./RegexTag"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
export class And extends TagsFilter {
|
||||
public and: TagsFilter[]
|
||||
|
@ -72,6 +73,10 @@ export class And extends TagsFilter {
|
|||
return allChoices
|
||||
}
|
||||
|
||||
asJson(): TagConfigJson {
|
||||
return { and: this.and.map((a) => a.asJson()) }
|
||||
}
|
||||
|
||||
asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record<string, string>) {
|
||||
return this.and
|
||||
.map((t) => {
|
||||
|
@ -228,6 +233,15 @@ export class And extends TagsFilter {
|
|||
return And.construct(newAnds)
|
||||
}
|
||||
|
||||
/**
|
||||
* const raw = {"and": [{"or":["leisure=playground","playground!=forest"]},{"or":["leisure=playground","playground!=forest"]}]}
|
||||
* const parsed = TagUtils.Tag(raw)
|
||||
* parsed.optimize().asJson() // => {"or":["leisure=playground","playground!=forest"]}
|
||||
*
|
||||
* const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}]
|
||||
* const parsed = TagUtils.Tag(raw)
|
||||
* parsed.optimize().asJson() // => "advertising=screen"
|
||||
*/
|
||||
optimize(): TagsFilter | boolean {
|
||||
if (this.and.length === 0) {
|
||||
return true
|
||||
|
@ -289,9 +303,17 @@ export class And extends TagsFilter {
|
|||
optimized.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
} else if (v !== opt.value) {
|
||||
// detected an internal conflict
|
||||
} else {
|
||||
if (!v.match(opt.value)) {
|
||||
// We _know_ that for the key of the RegexTag `opt`, the value will be `v`.
|
||||
// As such, if `opt.value` cannot match `v`, we detected an internal conflict and can fail
|
||||
|
||||
return false
|
||||
} else {
|
||||
// Another tag already provided a _stricter_ value then this regex, so we can remove this one!
|
||||
optimized.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -369,10 +391,13 @@ export class And extends TagsFilter {
|
|||
const elements = containedOr.or.filter(
|
||||
(candidate) => !commonValues.some((cv) => cv.shadows(candidate))
|
||||
)
|
||||
if (elements.length > 0) {
|
||||
newOrs.push(Or.construct(elements))
|
||||
}
|
||||
|
||||
}
|
||||
if (newOrs.length > 0) {
|
||||
commonValues.push(And.construct(newOrs))
|
||||
}
|
||||
const result = new Or(commonValues).optimize()
|
||||
if (result === false) {
|
||||
return false
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { Tag } from "./Tag"
|
||||
|
||||
export default class ComparingTag implements TagsFilter {
|
||||
private readonly _key: string
|
||||
private readonly _predicate: (value: string) => boolean
|
||||
private readonly _representation: string
|
||||
private readonly _representation: "<" | ">" | "<=" | ">="
|
||||
private readonly _boundary: string
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
predicate: (value: string | undefined) => boolean,
|
||||
representation: string = ""
|
||||
representation: "<" | ">" | "<=" | ">=",
|
||||
boundary: string
|
||||
) {
|
||||
this._key = key
|
||||
this._predicate = predicate
|
||||
this._representation = representation
|
||||
this._boundary = boundary
|
||||
}
|
||||
|
||||
asChange(properties: Record<string, string>): { k: string; v: string }[] {
|
||||
|
@ -20,15 +25,64 @@ export default class ComparingTag implements TagsFilter {
|
|||
}
|
||||
|
||||
asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record<string, string>) {
|
||||
return this._key + this._representation
|
||||
return this._key + this._representation + this._boundary
|
||||
}
|
||||
|
||||
asOverpass(): string[] {
|
||||
throw "A comparable tag can not be used as overpass filter"
|
||||
}
|
||||
|
||||
/**
|
||||
* const tg = new ComparingTag("key", value => (Number(value) < 42), "<", "42")
|
||||
* const tg0 = new ComparingTag("key", value => (Number(value) < 42), "<", "42")
|
||||
* const tg1 = new ComparingTag("key", value => (Number(value) <= 42), "<=", "42")
|
||||
* const against = new ComparingTag("key", value => (Number(value) > 0), ">", "0")
|
||||
* tg.shadows(new Tag("key", "41")) // => true
|
||||
* tg.shadows(new Tag("key", "0")) // => true
|
||||
* tg.shadows(new Tag("key", "43")) // => false
|
||||
* tg.shadows(new Tag("key", "0")) // => true
|
||||
* tg.shadows(tg) // => true
|
||||
* tg.shadows(tg0) // => true
|
||||
* tg.shadows(against) // => false
|
||||
* tg1.shadows(tg0) // => true
|
||||
* tg0.shadows(tg1) // => false
|
||||
*
|
||||
*/
|
||||
shadows(other: TagsFilter): boolean {
|
||||
return other === this
|
||||
if (other === this) {
|
||||
return true
|
||||
}
|
||||
if (other instanceof ComparingTag) {
|
||||
if (other._key !== this._key) {
|
||||
return false
|
||||
}
|
||||
const selfDesc = this._representation === "<" || this._representation === "<="
|
||||
const otherDesc = other._representation === "<" || other._representation === "<="
|
||||
if (selfDesc !== otherDesc) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
this._boundary === other._boundary &&
|
||||
this._representation === other._representation
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (this._predicate(other._boundary)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (other instanceof Tag) {
|
||||
if (other.key !== this._key) {
|
||||
return false
|
||||
}
|
||||
if (this.matchesProperties({ [other.key]: other.value })) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
isUsableAsAnswer(): boolean {
|
||||
|
@ -38,7 +92,7 @@ export default class ComparingTag implements TagsFilter {
|
|||
/**
|
||||
* Checks if the properties match
|
||||
*
|
||||
* const t = new ComparingTag("key", (x => Number(x) < 42))
|
||||
* const t = new ComparingTag("key", (x => Number(x) < 42), "<", "42")
|
||||
* t.matchesProperties({key: 42}) // => false
|
||||
* t.matchesProperties({key: 41}) // => true
|
||||
* t.matchesProperties({key: 0}) // => true
|
||||
|
@ -56,6 +110,10 @@ export default class ComparingTag implements TagsFilter {
|
|||
return []
|
||||
}
|
||||
|
||||
asJson(): TagConfigJson {
|
||||
return this._key + this._representation
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { TagUtils } from "./TagUtils"
|
||||
import { And } from "./And"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
export class Or extends TagsFilter {
|
||||
public or: TagsFilter[]
|
||||
|
@ -27,6 +28,10 @@ export class Or extends TagsFilter {
|
|||
return false
|
||||
}
|
||||
|
||||
asJson(): TagConfigJson {
|
||||
return { or: this.or.map((o) => o.asJson()) }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* import {Tag} from "./Tag";
|
||||
|
@ -157,6 +162,12 @@ export class Or extends TagsFilter {
|
|||
return Or.construct(newOrs)
|
||||
}
|
||||
|
||||
/**
|
||||
* const raw = {"or": [{"and":["leisure=playground","playground!=forest"]},{"and":["leisure=playground","playground!=forest"]}]}
|
||||
* const parsed = TagUtils.Tag(raw)
|
||||
* parsed.optimize().asJson() // => {"and":["leisure=playground","playground!=forest"]}
|
||||
*
|
||||
*/
|
||||
optimize(): TagsFilter | boolean {
|
||||
if (this.or.length === 0) {
|
||||
return false
|
||||
|
@ -174,9 +185,9 @@ export class Or extends TagsFilter {
|
|||
const newOrs: TagsFilter[] = []
|
||||
let containedAnds: And[] = []
|
||||
for (const tf of optimized) {
|
||||
if (tf instanceof Or) {
|
||||
if (tf["or"]) {
|
||||
// expand all the nested ors...
|
||||
newOrs.push(...tf.or)
|
||||
newOrs.push(...tf["or"])
|
||||
} else if (tf instanceof And) {
|
||||
// partition of all the ands
|
||||
containedAnds.push(tf)
|
||||
|
@ -191,7 +202,7 @@ export class Or extends TagsFilter {
|
|||
const cleanedContainedANds: And[] = []
|
||||
outer: for (let containedAnd of containedAnds) {
|
||||
for (const known of newOrs) {
|
||||
// input for optimazation: (K=V | (X=Y & K=V))
|
||||
// input for optimization: (K=V | (X=Y & K=V))
|
||||
// containedAnd: (X=Y & K=V)
|
||||
// newOrs (and thus known): (K=V) --> false
|
||||
const cleaned = containedAnd.removePhraseConsideredKnown(known, false)
|
||||
|
@ -236,16 +247,21 @@ export class Or extends TagsFilter {
|
|||
const elements = containedAnd.and.filter(
|
||||
(candidate) => !commonValues.some((cv) => cv.shadows(candidate))
|
||||
)
|
||||
if (elements.length == 0) {
|
||||
continue
|
||||
}
|
||||
newAnds.push(And.construct(elements))
|
||||
}
|
||||
|
||||
if (newAnds.length > 0) {
|
||||
commonValues.push(Or.construct(newAnds))
|
||||
}
|
||||
|
||||
const result = new And(commonValues).optimize()
|
||||
if (result === true) {
|
||||
return true
|
||||
} else if (result === false) {
|
||||
// neutral element: skip
|
||||
} else {
|
||||
} else if (commonValues.length > 0) {
|
||||
newOrs.push(And.construct(commonValues))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Tag } from "./Tag"
|
||||
import { TagsFilter } from "./TagsFilter"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
export class RegexTag extends TagsFilter {
|
||||
public readonly key: RegExp | string
|
||||
|
@ -11,6 +12,9 @@ export class RegexTag extends TagsFilter {
|
|||
super()
|
||||
this.key = key
|
||||
this.value = value
|
||||
if (this.value instanceof RegExp && ("" + this.value).startsWith("^(^(")) {
|
||||
throw "Detected a duplicate start marker ^(^( in a regextag:" + this.value
|
||||
}
|
||||
this.invert = invert
|
||||
this.matchesEmpty = RegexTag.doesMatch("", this.value)
|
||||
}
|
||||
|
@ -41,11 +45,21 @@ export class RegexTag extends TagsFilter {
|
|||
return possibleRegex.test(fromTag)
|
||||
}
|
||||
|
||||
private static source(r: string | RegExp) {
|
||||
private static source(r: string | RegExp, includeStartMarker: boolean = true) {
|
||||
if (typeof r === "string") {
|
||||
return r
|
||||
}
|
||||
return r.source
|
||||
if (r === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const src = r.source
|
||||
if (includeStartMarker) {
|
||||
return src
|
||||
}
|
||||
if (src.startsWith("^(") && src.endsWith(")$")) {
|
||||
return src.substring(2, src.length - 2)
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,6 +96,24 @@ export class RegexTag extends TagsFilter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* import { TagUtils } from "./TagUtils";
|
||||
*
|
||||
* const t = TagUtils.Tag("a~b")
|
||||
* t.asJson() // => "a~b"
|
||||
*
|
||||
* const t = TagUtils.Tag("a=")
|
||||
* t.asJson() // => "a="
|
||||
*/
|
||||
asJson(): TagConfigJson {
|
||||
const v = RegexTag.source(this.value, false)
|
||||
if (typeof this.key === "string") {
|
||||
const oper = typeof this.value === "string" ? "=" : "~"
|
||||
return `${this.key}${this.invert ? "!" : ""}${oper}${v}`
|
||||
}
|
||||
return `${this.key.source}${this.invert ? "!" : ""}~~${v}`
|
||||
}
|
||||
|
||||
isUsableAsAnswer(): boolean {
|
||||
return false
|
||||
}
|
||||
|
@ -293,7 +325,7 @@ export class RegexTag extends TagsFilter {
|
|||
if (typeof this.key === "string") {
|
||||
return [this.key]
|
||||
}
|
||||
throw "Key cannot be determined as it is a regex"
|
||||
return []
|
||||
}
|
||||
|
||||
usedTags(): { key: string; value: string }[] {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { Tag } from "./Tag"
|
||||
import { Utils } from "../../Utils"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
/**
|
||||
* The substituting-tag uses the tags of a feature a variables and replaces them.
|
||||
|
@ -45,6 +46,10 @@ export default class SubstitutingTag implements TagsFilter {
|
|||
)
|
||||
}
|
||||
|
||||
asJson(): TagConfigJson {
|
||||
return this._key + (this._invert ? "!" : "") + ":=" + this._value
|
||||
}
|
||||
|
||||
asOverpass(): string[] {
|
||||
throw "A variable with substitution can not be used to query overpass"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import { TagsFilter } from "./TagsFilter"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
export class Tag extends TagsFilter {
|
||||
public key: string
|
||||
|
@ -67,6 +68,10 @@ export class Tag extends TagsFilter {
|
|||
return [`["${this.key}"="${this.value}"]`]
|
||||
}
|
||||
|
||||
asJson(): TagConfigJson {
|
||||
return this.key + "=" + this.value
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
const t = new Tag("key", "value")
|
||||
|
|
|
@ -15,8 +15,9 @@ type Tags = Record<string, string>
|
|||
export type UploadableTag = Tag | SubstitutingTag | And
|
||||
|
||||
export class TagUtils {
|
||||
public static readonly comparators: ReadonlyArray<[string, (a: number, b: number) => boolean]> =
|
||||
[
|
||||
public static readonly comparators: ReadonlyArray<
|
||||
["<" | ">" | "<=" | ">=", (a: number, b: number) => boolean]
|
||||
> = [
|
||||
["<=", (a, b) => a <= b],
|
||||
[">=", (a, b) => a >= b],
|
||||
["<", (a, b) => a < b],
|
||||
|
@ -324,6 +325,14 @@ export class TagUtils {
|
|||
return tags
|
||||
}
|
||||
|
||||
static optimzeJson(json: TagConfigJson): TagConfigJson | boolean {
|
||||
const optimized = TagUtils.Tag(json).optimize()
|
||||
if (optimized === true || optimized === false) {
|
||||
return optimized
|
||||
}
|
||||
return optimized.asJson()
|
||||
}
|
||||
|
||||
/**
|
||||
* Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set.
|
||||
*
|
||||
|
@ -735,11 +744,10 @@ export class TagUtils {
|
|||
const tag = json as string
|
||||
for (const [operator, comparator] of TagUtils.comparators) {
|
||||
if (tag.indexOf(operator) >= 0) {
|
||||
const split = Utils.SplitFirst(tag, operator)
|
||||
|
||||
let val = Number(split[1].trim())
|
||||
const split = Utils.SplitFirst(tag, operator).map((v) => v.trim())
|
||||
let val = Number(split[1])
|
||||
if (isNaN(val)) {
|
||||
val = new Date(split[1].trim()).getTime()
|
||||
val = new Date(split[1]).getTime()
|
||||
}
|
||||
|
||||
const f = (value: string | number | undefined) => {
|
||||
|
@ -762,7 +770,7 @@ export class TagUtils {
|
|||
}
|
||||
return comparator(b, val)
|
||||
}
|
||||
return new ComparingTag(split[0], f, operator + val)
|
||||
return new ComparingTag(split[0], f, operator, "" + val)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -861,6 +869,27 @@ export class TagUtils {
|
|||
return TagUtils.keyCounts.keys[key]
|
||||
}
|
||||
|
||||
public static GetPopularity(tag: TagsFilter): number | undefined {
|
||||
if (tag instanceof And) {
|
||||
return Math.min(...Utils.NoNull(tag.and.map((t) => TagUtils.GetPopularity(t)))) - 1
|
||||
}
|
||||
if (tag instanceof Or) {
|
||||
return Math.max(...Utils.NoNull(tag.or.map((t) => TagUtils.GetPopularity(t)))) + 1
|
||||
}
|
||||
if (tag instanceof Tag) {
|
||||
return TagUtils.GetCount(tag.key, tag.value)
|
||||
}
|
||||
if (tag instanceof RegexTag) {
|
||||
const key = tag.key
|
||||
if (key instanceof RegExp || tag.invert || tag.isNegative()) {
|
||||
return undefined
|
||||
}
|
||||
return TagUtils.GetCount(key)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
private static order(a: TagsFilter, b: TagsFilter, usePopularity: boolean): number {
|
||||
const rta = a instanceof RegexTag
|
||||
const rtb = b instanceof RegexTag
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
|
||||
export abstract class TagsFilter {
|
||||
abstract asOverpass(): string[]
|
||||
|
||||
|
@ -17,6 +19,8 @@ export abstract class TagsFilter {
|
|||
properties: Record<string, string>
|
||||
): string
|
||||
|
||||
abstract asJson(): TagConfigJson
|
||||
|
||||
abstract usedKeys(): string[]
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,7 +31,7 @@ export class Stores {
|
|||
* @param promise
|
||||
* @constructor
|
||||
*/
|
||||
public static FromPromise<T>(promise: Promise<T>): Store<T> {
|
||||
public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> {
|
||||
const src = new UIEventSource<T>(undefined)
|
||||
promise?.then((d) => src.setData(d))
|
||||
promise?.catch((err) => console.warn("Promise failed:", err))
|
||||
|
@ -97,7 +97,10 @@ export abstract class Store<T> implements Readable<T> {
|
|||
abstract map<J>(f: (t: T) => J): Store<J>
|
||||
abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J>
|
||||
|
||||
public mapD<J>(f: (t: T) => J, extraStoresToWatch?: Store<any>[]): Store<J> {
|
||||
public mapD<J>(
|
||||
f: (t: Exclude<T, undefined | null>) => J,
|
||||
extraStoresToWatch?: Store<any>[]
|
||||
): Store<J> {
|
||||
return this.map((t) => {
|
||||
if (t === undefined) {
|
||||
return undefined
|
||||
|
@ -105,7 +108,7 @@ export abstract class Store<T> implements Readable<T> {
|
|||
if (t === null) {
|
||||
return null
|
||||
}
|
||||
return f(t)
|
||||
return f(<Exclude<T, undefined | null>>t)
|
||||
}, extraStoresToWatch)
|
||||
}
|
||||
|
||||
|
@ -201,24 +204,36 @@ export abstract class Store<T> implements Readable<T> {
|
|||
mapped.addCallbackAndRun((newEventSource) => {
|
||||
if (newEventSource === null) {
|
||||
sink.setData(null)
|
||||
} else if (newEventSource === undefined) {
|
||||
return
|
||||
}
|
||||
if (newEventSource === undefined) {
|
||||
sink.setData(undefined)
|
||||
} else if (!seenEventSources.has(newEventSource)) {
|
||||
return
|
||||
}
|
||||
if (seenEventSources.has(newEventSource)) {
|
||||
// Already seen, so we don't have to add a callback, just update the value
|
||||
sink.setData(newEventSource.data)
|
||||
return
|
||||
}
|
||||
seenEventSources.add(newEventSource)
|
||||
newEventSource.addCallbackAndRun((resultData) => {
|
||||
if (mapped.data === newEventSource) {
|
||||
sink.setData(resultData)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Already seen, so we don't have to add a callback, just update the value
|
||||
sink.setData(newEventSource.data)
|
||||
}
|
||||
})
|
||||
|
||||
return sink
|
||||
}
|
||||
|
||||
public bindD<X>(f: (t: Exclude<T, undefined | null>) => Store<X>): Store<X> {
|
||||
return this.bind((t) => {
|
||||
if (t === undefined || t === null) {
|
||||
return <undefined | null>t
|
||||
}
|
||||
return f(<Exclude<T, undefined | null>>t)
|
||||
})
|
||||
}
|
||||
public stabilized(millisToStabilize): Store<T> {
|
||||
if (Utils.runningFromConsole) {
|
||||
return this
|
||||
|
@ -603,7 +618,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
*/
|
||||
public static FromPromiseWithErr<T>(
|
||||
promise: Promise<T>
|
||||
): UIEventSource<{ success: T } | { error: any }> {
|
||||
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
||||
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
||||
promise?.then((d) => src.setData({ success: d }))
|
||||
promise?.catch((err) => src.setData({ error: err }))
|
||||
|
@ -771,18 +786,26 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
* Monoidal map which results in a read-only store. 'undefined' is passed 'as is'
|
||||
* Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)'
|
||||
*/
|
||||
public mapD<J>(f: (t: T) => J, extraSources: Store<any>[] = []): Store<J | undefined> {
|
||||
public mapD<J>(
|
||||
f: (t: Exclude<T, undefined | null>) => J,
|
||||
extraSources: Store<any>[] = []
|
||||
): Store<J | undefined> {
|
||||
return new MappedStore(
|
||||
this,
|
||||
(t) => {
|
||||
if (t === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return f(t)
|
||||
if (t === null) {
|
||||
return null
|
||||
}
|
||||
return f(<Exclude<T, undefined | null>>t)
|
||||
},
|
||||
extraSources,
|
||||
this._callbacks,
|
||||
this.data === undefined ? undefined : f(this.data)
|
||||
this.data === undefined || this.data === null
|
||||
? <undefined | null>this.data
|
||||
: f(<any>this.data)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export class MangroveIdentity {
|
|||
const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined)
|
||||
this.keypair = keypairEventSource
|
||||
mangroveIdentity.addCallbackAndRunD(async (data) => {
|
||||
if (data === "") {
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data))
|
||||
|
|
|
@ -40,15 +40,26 @@ export interface P4CPicture {
|
|||
export default class NearbyImagesSearch {
|
||||
public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const
|
||||
public static readonly apiUrls = ["https://api.flickr.com"]
|
||||
private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[]
|
||||
private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number } | undefined>[]
|
||||
private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([])
|
||||
public readonly store: Store<P4CPicture[]> = this._store
|
||||
public readonly allDone: Store<boolean>
|
||||
private readonly _options: NearbyImageOptions
|
||||
|
||||
constructor(options: NearbyImageOptions, features: IndexedFeatureSource) {
|
||||
this.individualStores = NearbyImagesSearch.services.map((s) =>
|
||||
NearbyImagesSearch.buildPictureFetcher(options, s)
|
||||
)
|
||||
|
||||
const allDone = new UIEventSource(false)
|
||||
this.allDone = allDone
|
||||
const self = this
|
||||
function updateAllDone(){
|
||||
const stillRunning = self.individualStores.some(store => store.data === undefined)
|
||||
allDone.setData(!stillRunning)
|
||||
}
|
||||
self.individualStores.forEach(s => s.addCallback(_ => updateAllDone()))
|
||||
|
||||
this._options = options
|
||||
if (features !== undefined) {
|
||||
const osmImages = new ImagesInLoadedDataFetcher(features).fetchAround({
|
||||
|
@ -93,13 +104,17 @@ export default class NearbyImagesSearch {
|
|||
private static buildPictureFetcher(
|
||||
options: NearbyImageOptions,
|
||||
fetcher: P4CService
|
||||
): Store<{ images: P4CPicture[]; beforeFilter: number }> {
|
||||
const p4cStore = Stores.FromPromise<P4CPicture[]>(
|
||||
): Store<{ images: P4CPicture[]; beforeFilter: number } | null | undefined> {
|
||||
const p4cStore = Stores.FromPromiseWithErr<P4CPicture[]>(
|
||||
NearbyImagesSearch.fetchImages(options, fetcher)
|
||||
)
|
||||
const searchRadius = options.searchRadius ?? 100
|
||||
return p4cStore.map(
|
||||
(images) => {
|
||||
return p4cStore.mapD(
|
||||
(imagesState) => {
|
||||
if(imagesState["error"]){
|
||||
return null
|
||||
}
|
||||
let images = imagesState["success"]
|
||||
if (images === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export default class Constants {
|
|||
"gps_track",
|
||||
"range",
|
||||
"last_click",
|
||||
"favourite",
|
||||
] as const
|
||||
/**
|
||||
* Special layers which are not included in a theme by default
|
||||
|
@ -131,6 +132,8 @@ export default class Constants {
|
|||
"clock",
|
||||
"invalid",
|
||||
"close",
|
||||
"heart",
|
||||
"heart_outline",
|
||||
] as const
|
||||
public static readonly defaultPinIcons: string[] = <any>Constants._defaultPinIcons
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Translation } from "../UI/i18n/Translation"
|
||||
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||
import Translations from "../UI/i18n/Translations"
|
||||
import { Store } from "../Logic/UIEventSource"
|
||||
import BaseUIElement from "../UI/BaseUIElement"
|
||||
import Toggle from "../UI/Input/Toggle"
|
||||
|
||||
/**
|
||||
* A 'denomination' is one way to write a certain quantity.
|
||||
* For example, 'meter', 'kilometer', 'mile' and 'foot' are all possible ways to quantify 'length'
|
||||
*/
|
||||
export class Denomination {
|
||||
public readonly canonical: string
|
||||
public readonly _canonicalSingular: string
|
||||
|
@ -53,8 +54,8 @@ export class Denomination {
|
|||
|
||||
/**
|
||||
* Create a representation of the given value
|
||||
* @param value: the value from OSM
|
||||
* @param actAsDefault: if set and the value can be parsed as number, will be parsed and trimmed
|
||||
* @param value the value from OSM
|
||||
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
||||
*
|
||||
* const unit = new Denomination({
|
||||
* canonicalDenomination: "m",
|
||||
|
@ -82,6 +83,8 @@ export class Denomination {
|
|||
* unit.canonicalValue("42", true) // =>"42"
|
||||
* unit.canonicalValue("42 m", true) // =>"42"
|
||||
* unit.canonicalValue("42 meter", true) // =>"42"
|
||||
*
|
||||
*
|
||||
*/
|
||||
public canonicalValue(value: string, actAsDefault: boolean): string {
|
||||
if (value === undefined) {
|
||||
|
|
|
@ -24,6 +24,7 @@ export class MenuState {
|
|||
public static readonly _menuviewTabs = [
|
||||
"about",
|
||||
"settings",
|
||||
"favourites",
|
||||
"community",
|
||||
"privacy",
|
||||
"advanced",
|
||||
|
@ -78,6 +79,11 @@ export class MenuState {
|
|||
this.highlightedUserSetting.setData(undefined)
|
||||
}
|
||||
})
|
||||
this.menuViewTab.addCallbackD((tab) => {
|
||||
if (tab !== "settings") {
|
||||
this.highlightedUserSetting.setData(undefined)
|
||||
}
|
||||
})
|
||||
this.themeViewTab.addCallbackAndRun((tab) => {
|
||||
if (tab !== "filters") {
|
||||
this.highlightedLayerInFilters.setData(undefined)
|
||||
|
|