forked from MapComplete/MapComplete
Feature: allow to move and snap to a layer, fix #2120
This commit is contained in:
parent
eb89427bfc
commit
fdedb75954
34 changed files with 824 additions and 301 deletions
159
Docs/Layers/bicycle_assisted_repair_workshop.md
Normal file
159
Docs/Layers/bicycle_assisted_repair_workshop.md
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)
|
||||||
|
|
||||||
|
# bicycle_assisted_repair_workshop
|
||||||
|
|
||||||
|
This layer is based on [assisted_repair](../Layers/assisted_repair.md)
|
||||||
|
|
||||||
|
A self-assisted workshop is a location where people can come and repair their goods with help of volunteers and with the tools available at the given location. A repair café is a type of event organized regularly along the same principles.
|
||||||
|
|
||||||
|
- This layer is shown at zoomlevel **11** and higher
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
1. [Themes using this layer](#themes-using-this-layer)
|
||||||
|
2. [Basic tags for this layer](#basic-tags-for-this-layer)
|
||||||
|
3. [Supported attributes](#supported-attributes)
|
||||||
|
- [images](#images)
|
||||||
|
- [preset_description](#preset_description)
|
||||||
|
- [name](#name)
|
||||||
|
- [opening_hours_by_appointment](#opening_hours_by_appointment)
|
||||||
|
- [Opening hours](#opening-hours)
|
||||||
|
- [phone](#phone)
|
||||||
|
- [email](#email)
|
||||||
|
- [website](#website)
|
||||||
|
- [mastodon](#mastodon)
|
||||||
|
- [facebook](#facebook)
|
||||||
|
- [item:repair](#itemrepair)
|
||||||
|
- [leftover-questions](#leftover-questions)
|
||||||
|
- [move-button](#move-button)
|
||||||
|
- [delete-button](#delete-button)
|
||||||
|
- [lod](#lod)
|
||||||
|
|
||||||
|
## Themes using this layer
|
||||||
|
|
||||||
|
- [cyclofix](https://mapcomplete.org/cyclofix)
|
||||||
|
|
||||||
|
## Basic tags for this layer
|
||||||
|
|
||||||
|
Elements must match **all** of the following expressions:
|
||||||
|
|
||||||
|
0. <a href='https://wiki.openstreetmap.org/wiki/Key:repair' target='_blank'>repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:repair%3Dassisted_self_service' target='_blank'>assisted_self_service</a>
|
||||||
|
1. <a href='https://wiki.openstreetmap.org/wiki/Key:bicycle:repair' target='_blank'>bicycle:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:bicycle:repair%3Dyes' target='_blank'>yes</a> | <a href='https://wiki.openstreetmap.org/wiki/Key:service:bicycle:repair' target='_blank'>service:bicycle:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:bicycle:repair%3Dyes' target='_blank'>yes</a>
|
||||||
|
|
||||||
|
[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B%28%20%20%20%20nwr%5B%22repair%22%3D%22assisted_self_service%22%5D%5B%22bicycle%3Arepair%22%3D%22yes%22%5D%28%7B%7Bbbox%7D%7D%29%3B%0A%20%20%20%20nwr%5B%22repair%22%3D%22assisted_self_service%22%5D%5B%22service%3Abicycle%3Arepair%22%3D%22yes%22%5D%28%7B%7Bbbox%7D%7D%29%3B%0A%29%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B)
|
||||||
|
|
||||||
|
## Supported attributes
|
||||||
|
|
||||||
|
**Warning:**,this quick overview is incomplete,
|
||||||
|
|
||||||
|
| attribute | type | values which are supported by this layer |
|
||||||
|
-----|-----|----- |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/name#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/name/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/opening_hours#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/opening_hours/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | ["by appointment"](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D"by appointment") |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/phone#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/phone/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/email#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/email/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/website#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/website/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/contact:mastodon#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/contact%3Amastodon/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [contact:mastodon](https://wiki.openstreetmap.org/wiki/Key:contact:mastodon) | [fediverse](../SpecialInputElements.md#fediverse) | |
|
||||||
|
| <a target="_blank" href='https://taginfo.openstreetmap.org/keys/contact:facebook#values'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a> <a target="_blank" href='https://taghistory.raifer.tech/?#***/contact%3Afacebook/'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a> [contact:facebook](https://wiki.openstreetmap.org/wiki/Key:contact:facebook) | [url](../SpecialInputElements.md#url) | |
|
||||||
|
|
||||||
|
### images
|
||||||
|
This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` and shows the button to upload new images
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{image_carousel()}{image_upload()}*
|
||||||
|
|
||||||
|
### preset_description
|
||||||
|
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{preset_description()}*
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
The question is `What is the name of this repair workshop?`
|
||||||
|
*This workshop is called <b>{name}</b>* is shown if `name` is set
|
||||||
|
|
||||||
|
### opening_hours_by_appointment
|
||||||
|
|
||||||
|
The question is `What are the opening hours of {title()}?`
|
||||||
|
*<h3>Opening hours</h3>{opening_hours_table(opening_hours)}* is shown if `opening_hours` is set
|
||||||
|
|
||||||
|
- *Only by appointment* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:opening_hours' target='_blank'>opening_hours</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D"by appointment"' target='_blank'>"by appointment"</a>
|
||||||
|
- *Only by appointment* is shown if with opening_hours~^("by appointment"|by appointment)$. _This option cannot be chosen as answer_
|
||||||
|
- *Marked as closed for an unspecified time* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:opening_hours' target='_blank'>opening_hours</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3Dclosed' target='_blank'>closed</a>. _This option cannot be chosen as answer_
|
||||||
|
|
||||||
|
### phone
|
||||||
|
|
||||||
|
The question is `What is the phone number of {title()}?`
|
||||||
|
*{link(&LBRACEphone&RBRACE,tel:&LBRACEphone&RBRACE,,,,)}* is shown if `phone` is set
|
||||||
|
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/layers/questions/phone.svg' style='width: 3rem; height: 3rem'> *{link(&LBRACEcontact:phone&RBRACE,tel:&LBRACEcontact:phone&RBRACE,,,,)}* is shown if with contact:phone~.+. _This option cannot be chosen as answer_
|
||||||
|
|
||||||
|
This tagrendering has labels
|
||||||
|
`contact`
|
||||||
|
|
||||||
|
### email
|
||||||
|
|
||||||
|
The question is `What is the email address of {title()}?`
|
||||||
|
*<a href='mailto:{email}' target='_blank' rel='noopener'>{email}</a>* is shown if `email` is set
|
||||||
|
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/svg/envelope.svg' style='width: 3rem; height: 3rem'> *<a href='mailto:{contact:email}' target='_blank' rel='noopener'>{contact:email}</a>* is shown if with contact:email~.+. _This option cannot be chosen as answer_
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/svg/envelope.svg' style='width: 3rem; height: 3rem'> *<a href='mailto:{operator:email}' target='_blank' rel='noopener'>{operator:email}</a>* is shown if with operator:email~.+. _This option cannot be chosen as answer_
|
||||||
|
|
||||||
|
This tagrendering has labels
|
||||||
|
`contact`
|
||||||
|
|
||||||
|
### website
|
||||||
|
|
||||||
|
The question is `What is the website of {title()}?`
|
||||||
|
*<a href='{website}' rel='nofollow noopener noreferrer' target='_blank'>{website}</a>* is shown if `website` is set
|
||||||
|
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/layers/icons/website.svg' style='width: 3rem; height: 3rem'> *<a href='{contact:website}' rel='nofollow noopener noreferrer' target='_blank'>{contact:website}</a>* is shown if with contact:website~.+. _This option cannot be chosen as answer_
|
||||||
|
|
||||||
|
This tagrendering has labels
|
||||||
|
`contact`
|
||||||
|
|
||||||
|
### mastodon
|
||||||
|
Shows and asks for the mastodon handle
|
||||||
|
The question is `What is the Mastodon-handle of {title()}?`
|
||||||
|
*{fediverse_link(contact:mastodon)}* is shown if `contact:mastodon` is set
|
||||||
|
|
||||||
|
### facebook
|
||||||
|
Shows and asks for the facebook handle
|
||||||
|
The question is `What is the facebook page of of {title()}?`
|
||||||
|
*{link(Facebook page,&LBRACEcontact:facebook&RBRACE,,,,)}<div class='subtle text-sm'>Facebook is known to harm mental health, manipulate public opinion and cause hate. Try to use healthier alternatives</div>* is shown if `contact:facebook` is set
|
||||||
|
|
||||||
|
### item:repair
|
||||||
|
|
||||||
|
The question is `What type of items are repaired here?`
|
||||||
|
|
||||||
|
- *Mobile phones are repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:mobile_phone:repair' target='_blank'>service:mobile_phone:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:mobile_phone:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:mobile_phone:repair' target='_blank'>service:mobile_phone:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:mobile_phone:repair%3Dno' target='_blank'>no</a>
|
||||||
|
- *Computers are repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:computer:repair' target='_blank'>service:computer:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:computer:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:computer:repair' target='_blank'>service:computer:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:computer:repair%3Dno' target='_blank'>no</a>
|
||||||
|
- *Bicycles are repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:bicycle:repair' target='_blank'>service:bicycle:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:bicycle:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:bicycle:repair' target='_blank'>service:bicycle:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:bicycle:repair%3Dno' target='_blank'>no</a>
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/layers/recycling/small_electrical_appliances.svg' style='width: 3rem; height: 3rem'> *Electronic devices are repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:electronics:repair' target='_blank'>service:electronics:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:electronics:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:electronics:repair' target='_blank'>service:electronics:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:electronics:repair%3Dno' target='_blank'>no</a>
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/layers/recycling/furniture.svg' style='width: 3rem; height: 3rem'> *Furniture is repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:furniture:repair' target='_blank'>service:furniture:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:furniture:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:furniture:repair' target='_blank'>service:furniture:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:furniture:repair%3Dno' target='_blank'>no</a>
|
||||||
|
- <img src='https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/./assets/layers/recycling/clothes.svg' style='width: 3rem; height: 3rem'> *Clothes are repaired here* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:service:clothes:repair' target='_blank'>service:clothes:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:clothes:repair%3Dyes' target='_blank'>yes</a>. Unselecting this answer will add <a href='https://wiki.openstreetmap.org/wiki/Key:service:clothes:repair' target='_blank'>service:clothes:repair</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:service:clothes:repair%3Dno' target='_blank'>no</a>
|
||||||
|
|
||||||
|
### leftover-questions
|
||||||
|
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{questions( ,)}*
|
||||||
|
|
||||||
|
### move-button
|
||||||
|
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{move_button()}*
|
||||||
|
|
||||||
|
### delete-button
|
||||||
|
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{delete_button()}*
|
||||||
|
|
||||||
|
### lod
|
||||||
|
|
||||||
|
_This tagrendering has no question and is thus read-only_
|
||||||
|
*{linked_data_from_website()}*
|
||||||
|
|
||||||
|
This tagrendering has labels
|
||||||
|
`added_by_default`
|
||||||
|
|
||||||
|
|
||||||
|
This document is autogenerated from [assets/themes/cyclofix/cyclofix.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/cyclofix/cyclofix.json)
|
|
@ -34,6 +34,9 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a wall, cliff or rock"
|
||||||
|
},
|
||||||
"minzoom": 18,
|
"minzoom": 18,
|
||||||
"title": {
|
"title": {
|
||||||
"render": {
|
"render": {
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
"ca": "Vies ciclistes i carreteres",
|
"ca": "Vies ciclistes i carreteres",
|
||||||
"cs": "Cyklostezky a silnice"
|
"cs": "Cyklostezky a silnice"
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a road or a cycleway",
|
||||||
|
"nl": "een weg, straat of fietspad"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "All infrastructure that someone can cycle over, accompanied with questions about this infrastructure",
|
"en": "All infrastructure that someone can cycle over, accompanied with questions about this infrastructure",
|
||||||
"nl": "Alle infrastructuur waar je over kunt fietsen, met vragen over die infrastructuur",
|
"nl": "Alle infrastructuur waar je over kunt fietsen, met vragen over die infrastructuur",
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
"ca": "Interiors",
|
"ca": "Interiors",
|
||||||
"cs": "Vnitřní prostory"
|
"cs": "Vnitřní prostory"
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "an indoor wall",
|
||||||
|
"nl": "een binnenmuur"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Basic indoor mapping: shows room outlines",
|
"en": "Basic indoor mapping: shows room outlines",
|
||||||
"de": "Grundlegende Innenraumkartierung: zeigt Umrisse von Räumen",
|
"de": "Grundlegende Innenraumkartierung: zeigt Umrisse von Räumen",
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
"ca": "Vroades",
|
"ca": "Vroades",
|
||||||
"cs": "Obrubníky"
|
"cs": "Obrubníky"
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a kerb"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "A layer showing kerbs.",
|
"en": "A layer showing kerbs.",
|
||||||
"nl": "Een laag met stoepranden.",
|
"nl": "Een laag met stoepranden.",
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
"ca": "Camins per a vianants",
|
"ca": "Camins per a vianants",
|
||||||
"cs": "Cesty pro chodce"
|
"cs": "Cesty pro chodce"
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a pedestrian path"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer",
|
"en": "Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer",
|
||||||
"nl": "Pad voor voetgangers, in het bijzonder gebruikt voor navigatie binnen gebouwen en om aan toegangen vast te klikken in deze laag",
|
"nl": "Pad voor voetgangers, in het bijzonder gebruikt voor navigatie binnen gebouwen en om aan toegangen vast te klikken in deze laag",
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
"fr": "Abri",
|
"fr": "Abri",
|
||||||
"cs": "Přístřešek"
|
"cs": "Přístřešek"
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a shelter"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Layer showing shelter structures",
|
"en": "Layer showing shelter structures",
|
||||||
"de": "Eine Ebene, die verschiedene Bauformen von Unterständen zeigt",
|
"de": "Eine Ebene, die verschiedene Bauformen von Unterständen zeigt",
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
"zh_Hant": "特殊的內建圖層顯示所有牆壁與建築。這個圖層對於規畫要靠牆的東西 (例如 AED、郵筒、入口、地址、監視器等) 相當實用。這個圖層預設顯示而且無法由使用者開關。",
|
"zh_Hant": "特殊的內建圖層顯示所有牆壁與建築。這個圖層對於規畫要靠牆的東西 (例如 AED、郵筒、入口、地址、監視器等) 相當實用。這個圖層預設顯示而且無法由使用者開關。",
|
||||||
"pl": "Specjalna warstwa zabudowana zapewniająca wszystkie mury i budynki. Warstwa ta jest przydatna w ustawieniach wstępnych obiektów, które można umieścić przy ścianach (np. AED, skrzynki pocztowe, wejścia, adresy, kamery monitorujące itp.). Warstwa ta jest domyślnie niewidoczna i użytkownik nie może jej przełączać."
|
"pl": "Specjalna warstwa zabudowana zapewniająca wszystkie mury i budynki. Warstwa ta jest przydatna w ustawieniach wstępnych obiektów, które można umieścić przy ścianach (np. AED, skrzynki pocztowe, wejścia, adresy, kamery monitorujące itp.). Warstwa ta jest domyślnie niewidoczna i użytkownik nie może jej przełączać."
|
||||||
},
|
},
|
||||||
|
"snapName": {
|
||||||
|
"en": "a wall or building",
|
||||||
|
"nl": "een muur of gebouw"
|
||||||
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"or": [
|
"or": [
|
||||||
|
|
|
@ -1065,6 +1065,14 @@
|
||||||
"https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/AUTHORS.txt"
|
"https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/AUTHORS.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "snap.svg",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"authors": [
|
||||||
|
"Pieter Vander Vennet"
|
||||||
|
],
|
||||||
|
"sources": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "speech_bubble.svg",
|
"path": "speech_bubble.svg",
|
||||||
"license": "CC-BY-4.0",
|
"license": "CC-BY-4.0",
|
||||||
|
@ -1203,6 +1211,14 @@
|
||||||
],
|
],
|
||||||
"sources": []
|
"sources": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "unsnap.svg",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"authors": [
|
||||||
|
"Pieter Vander Vennet"
|
||||||
|
],
|
||||||
|
"sources": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "wikidata.svg",
|
"path": "wikidata.svg",
|
||||||
"license": "LOGO",
|
"license": "LOGO",
|
||||||
|
|
84
assets/svg/snap.svg
Normal file
84
assets/svg/snap.svg
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
||||||
|
sodipodi:docname="snap.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"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#999999"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:zoom="4.5168066"
|
||||||
|
inkscape:cx="40.51535"
|
||||||
|
inkscape:cy="42.39721"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="995"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1"><sodipodi:guide
|
||||||
|
position="315.49944,61.936443"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide2"
|
||||||
|
inkscape:locked="false" /></sodipodi:namedview><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-5,-5)"><path
|
||||||
|
id="path1-2"
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke-width:3.93092"
|
||||||
|
d="m 72.294942,72.07284 c 0.948679,-0.909931 1.380066,-2.234124 1.148907,-3.527969 L 69.995854,51.284128 C 69.614243,49.146951 67.469637,47.282834 65.914649,48.798037 L 59.882054,54.677556 44.886208,39.828561 c -1.383257,-1.369713 -3.807955,-0.909932 -4.298111,-0.288099 -1.707154,2.165786 -0.138139,3.968458 0.177549,4.304093 l 14.45874,15.372473 -6.032604,5.879513 c -1.554665,1.51554 0.253754,3.707343 2.380393,4.143751 l 17.16644,3.89041 c 1.287478,0.264342 2.622313,-0.132882 3.556328,-1.057866 z"
|
||||||
|
sodipodi:nodetypes="cccccsssccccc" /><g
|
||||||
|
id="g8" /><g
|
||||||
|
id="g10"
|
||||||
|
transform="rotate(-175.99037,61.199753,60.156378)"><g
|
||||||
|
id="path1"
|
||||||
|
inkscape:transform-center-x="1.8238832"
|
||||||
|
inkscape:transform-center-y="31.570993"><path
|
||||||
|
style="color:#000000;fill:#000000;stroke-linecap:round;-inkscape-stroke:none"
|
||||||
|
d="M 10,90 45,10"
|
||||||
|
id="path3" /><path
|
||||||
|
id="path4"
|
||||||
|
style="color:#000000;fill:#000009;stroke-linecap:round;-inkscape-stroke:none"
|
||||||
|
d="M 45.097656,5.0019531 A 5,5 0 0 0 43.177734,5.34375 5,5 0 0 0 40.419922,7.9960938 L 35.865234,18.40625 c 3.405007,0.669609 6.469474,2.331825 8.867188,4.679688 L 49.580078,12.003906 A 5,5 0 0 0 47.003906,5.4199219 5,5 0 0 0 45.097656,5.0019531 Z M 22.177734,49.691406 5.4199219,87.996094 a 5,5 0 0 0 2.5761719,6.583984 5,5 0 0 0 6.5839842,-2.576172 L 31.621094,53.052734 c -3.513941,-0.175553 -6.76611,-1.396873 -9.44336,-3.361328 z" /></g><path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-opacity:1"
|
||||||
|
id="path9"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="32.616085"
|
||||||
|
sodipodi:cy="35.55938"
|
||||||
|
sodipodi:rx="12.741771"
|
||||||
|
sodipodi:ry="12.741771"
|
||||||
|
sodipodi:start="0"
|
||||||
|
sodipodi:end="6.26046"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="M 45.357856,35.55938 A 12.741771,12.741771 0 0 1 32.688475,48.300945 12.741771,12.741771 0 0 1 19.875137,35.704157 12.741771,12.741771 0 0 1 32.398925,22.81946 12.741771,12.741771 0 0 1 45.354566,35.269844" /></g><path
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-opacity:1"
|
||||||
|
id="path10"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="26.067774"
|
||||||
|
sodipodi:cy="26.136267"
|
||||||
|
sodipodi:rx="12.741771"
|
||||||
|
sodipodi:ry="12.741771"
|
||||||
|
sodipodi:start="0"
|
||||||
|
sodipodi:end="6.26046"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="M 38.809545,26.136267 A 12.741771,12.741771 0 0 1 26.140164,38.877832 12.741771,12.741771 0 0 1 13.326826,26.281044 12.741771,12.741771 0 0 1 25.850614,13.396347 12.741771,12.741771 0 0 1 38.806255,25.846731" /></g></svg>
|
After Width: | Height: | Size: 4.2 KiB |
2
assets/svg/snap.svg.license
Normal file
2
assets/svg/snap.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
83
assets/svg/unsnap.svg
Normal file
83
assets/svg/unsnap.svg
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
||||||
|
sodipodi:docname="unsnap.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"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#999999"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:zoom="4.5168066"
|
||||||
|
inkscape:cx="51.695815"
|
||||||
|
inkscape:cy="69.186048"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="995"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1"><sodipodi:guide
|
||||||
|
position="315.49944,61.936443"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide2"
|
||||||
|
inkscape:locked="false" /></sodipodi:namedview><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-5,-5)"><path
|
||||||
|
id="path1-2"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke-width:3.93092"
|
||||||
|
d="m 91.670867,40.074491 c 0.948679,0.909931 1.380066,2.234124 1.148907,3.527969 l -3.447995,17.260743 c -0.381611,2.137177 -2.526217,4.001294 -4.081205,2.486091 L 79.257979,57.469775 64.262133,72.31877 c -1.383257,1.369713 -3.807955,0.909932 -4.298111,0.288099 -1.707154,-2.165786 -0.138139,-3.968458 0.177549,-4.304093 L 74.600311,52.930303 68.567707,47.05079 c -1.554665,-1.51554 0.253754,-3.707343 2.380393,-4.143751 l 17.16644,-3.89041 c 1.287478,-0.264342 2.622313,0.132882 3.556328,1.057866 z"
|
||||||
|
sodipodi:nodetypes="cccccsssccccc" /><g
|
||||||
|
id="g8"
|
||||||
|
transform="matrix(-1,0,0,1,132.686,0)" /><g
|
||||||
|
id="g10"
|
||||||
|
transform="matrix(0.99755231,-0.06992414,-0.06992414,-0.99755231,14.642674,124.44485)"><path
|
||||||
|
style="color:#000000;fill:#000000;stroke-linecap:round;-inkscape-stroke:none"
|
||||||
|
d="M 10,90 45,10"
|
||||||
|
id="path3" /><path
|
||||||
|
id="path4"
|
||||||
|
style="color:#000000;fill:#808080;stroke-linecap:round;-inkscape-stroke:none;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||||
|
d="M 45.097656,5.0019531 A 5,5 0 0 0 43.177734,5.34375 5,5 0 0 0 40.419922,7.9960938 L 35.865234,18.40625 c 3.405007,0.669609 6.469474,2.331825 8.867188,4.679688 L 49.580078,12.003906 A 5,5 0 0 0 47.003906,5.4199219 5,5 0 0 0 45.097656,5.0019531 Z M 22.177734,49.691406 5.4199219,87.996094 a 5,5 0 0 0 2.5761719,6.583984 5,5 0 0 0 6.5839842,-2.576172 L 31.621094,53.052734 c -3.513941,-0.175553 -6.76611,-1.396873 -9.44336,-3.361328 z" /><path
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-opacity:1"
|
||||||
|
id="path9"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="32.616085"
|
||||||
|
sodipodi:cy="35.55938"
|
||||||
|
sodipodi:rx="12.741771"
|
||||||
|
sodipodi:ry="12.741771"
|
||||||
|
sodipodi:start="0"
|
||||||
|
sodipodi:end="6.26046"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="M 45.357856,35.55938 A 12.741771,12.741771 0 0 1 32.688475,48.300945 12.741771,12.741771 0 0 1 19.875137,35.704157 12.741771,12.741771 0 0 1 32.398925,22.81946 12.741771,12.741771 0 0 1 45.354566,35.269844" /></g><path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-opacity:1"
|
||||||
|
id="path10"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="-106.61823"
|
||||||
|
sodipodi:cy="26.136267"
|
||||||
|
sodipodi:rx="12.741771"
|
||||||
|
sodipodi:ry="12.741771"
|
||||||
|
sodipodi:start="0"
|
||||||
|
sodipodi:end="6.26046"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m -93.876462,26.136267 a 12.741771,12.741771 0 0 1 -12.669378,12.741565 12.741771,12.741771 0 0 1 -12.81334,-12.596788 12.741771,12.741771 0 0 1 12.52379,-12.884697 12.741771,12.741771 0 0 1 12.955638,12.450384"
|
||||||
|
transform="scale(-1,1)" /></g></svg>
|
After Width: | Height: | Size: 4.2 KiB |
2
assets/svg/unsnap.svg.license
Normal file
2
assets/svg/unsnap.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||||
|
SPDX-License-Identifier: CC0
|
|
@ -1,16 +1,13 @@
|
||||||
{
|
{
|
||||||
"id": "mapcomplete-changes",
|
"id": "mapcomplete-changes",
|
||||||
"title": {
|
"title": {
|
||||||
"en": "Changes made with MapComplete",
|
"en": "Changes made with MapComplete"
|
||||||
"de": "Änderungen mit MapComplete vorgenommen"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"en": "This maps shows all the changes made with MapComplete",
|
|
||||||
"de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen"
|
|
||||||
},
|
},
|
||||||
"shortDescription": {
|
"shortDescription": {
|
||||||
"en": "Shows changes made by MapComplete",
|
"en": "Shows changes made by MapComplete"
|
||||||
"de": "Änderungen von MapComplete anzeigen"
|
},
|
||||||
|
"description": {
|
||||||
|
"en": "This maps shows all the changes made with MapComplete"
|
||||||
},
|
},
|
||||||
"icon": "./assets/svg/logo.svg",
|
"icon": "./assets/svg/logo.svg",
|
||||||
"hideFromOverview": true,
|
"hideFromOverview": true,
|
||||||
|
@ -21,8 +18,7 @@
|
||||||
{
|
{
|
||||||
"id": "mapcomplete-changes",
|
"id": "mapcomplete-changes",
|
||||||
"name": {
|
"name": {
|
||||||
"en": "Changeset centers",
|
"en": "Changeset centers"
|
||||||
"de": "Zentrum der Änderungssätze"
|
|
||||||
},
|
},
|
||||||
"minzoom": 0,
|
"minzoom": 0,
|
||||||
"source": {
|
"source": {
|
||||||
|
@ -32,48 +28,41 @@
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Changeset for {theme}",
|
"en": "Changeset for {theme}"
|
||||||
"de": "Änderungssatz für {theme}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Shows all MapComplete changes",
|
"en": "Shows all MapComplete changes"
|
||||||
"de": "Zeigt alle MapComplete-Änderungen"
|
|
||||||
},
|
},
|
||||||
"tagRenderings": [
|
"tagRenderings": [
|
||||||
{
|
{
|
||||||
"id": "show_changeset_id",
|
"id": "show_changeset_id",
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
|
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
|
||||||
"de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "contributor",
|
"id": "contributor",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What contributor did make this change?",
|
"en": "What contributor did make this change?"
|
||||||
"de": "Wer hat diese Änderung vorgenommen?"
|
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "user"
|
"key": "user"
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Change made by <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>"
|
||||||
"de": "Änderung von <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "theme-id",
|
"id": "theme-id",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What theme was used to make this change?",
|
"en": "What theme was used to make this change?"
|
||||||
"de": "Welches Thema wurde für die Änderung verwendet?"
|
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "theme"
|
"key": "theme"
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
|
"en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>"
|
||||||
"de": "Änderung mit Thema <a href='https://mapcomplete.org/{theme}'>{theme}</a>"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -82,23 +71,19 @@
|
||||||
"key": "locale"
|
"key": "locale"
|
||||||
},
|
},
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What locale (language) was this change made in?",
|
"en": "What locale (language) was this change made in?"
|
||||||
"de": "In welcher Benutzersprache wurde die Änderung vorgenommen?"
|
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "User locale is {locale}",
|
"en": "User locale is {locale}"
|
||||||
"de": "Benutzersprache ist {locale}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "host",
|
"id": "host",
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Change with with <a href='{host}'>{host}</a>",
|
"en": "Change with with <a href='{host}'>{host}</a>"
|
||||||
"de": "Änderung mit <a href='{host}'>{host}</a>"
|
|
||||||
},
|
},
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What host (website) was this change made with?",
|
"en": "What host (website) was this change made with?"
|
||||||
"de": "Mit welchem Host (Website) wurde diese Änderung vorgenommen?"
|
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "host"
|
"key": "host"
|
||||||
|
@ -119,12 +104,10 @@
|
||||||
{
|
{
|
||||||
"id": "version",
|
"id": "version",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What version of MapComplete was used to make this change?",
|
"en": "What version of MapComplete was used to make this change?"
|
||||||
"de": "Welche Version von MapComplete wurde für diese Änderung verwendet?"
|
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Made with {editor}",
|
"en": "Made with {editor}"
|
||||||
"de": "Erstellt mit {editor}"
|
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "editor"
|
"key": "editor"
|
||||||
|
@ -522,8 +505,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Themename contains {search}",
|
"en": "Themename contains {search}"
|
||||||
"de": "Themename enthält {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -539,8 +521,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Themename does <b>not</b> contain {search}",
|
"en": "Themename does <b>not</b> contain {search}"
|
||||||
"de": "Themenname enthält <b>nicht</b> {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -556,8 +537,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Made by contributor {search}",
|
"en": "Made by contributor {search}"
|
||||||
"de": "Der Name enthält <b>nicht</b> {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -573,8 +553,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "<b>Not</b> made by contributor {search}",
|
"en": "<b>Not</b> made by contributor {search}"
|
||||||
"de": "<b>Nicht</b> erstellt von Mitwirkendem {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -591,8 +570,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Made before {search}",
|
"en": "Made before {search}"
|
||||||
"de": "Erstellt nach {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -609,8 +587,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Made after {search}",
|
"en": "Made after {search}"
|
||||||
"de": "Erstellt nach {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -626,8 +603,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "User language (iso-code) {search}",
|
"en": "User language (iso-code) {search}"
|
||||||
"de": "Benutzersprache (ISO-Code) {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -643,8 +619,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Made with host {search}",
|
"en": "Made with host {search}"
|
||||||
"de": "Erstellt mit Host {search}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -655,8 +630,7 @@
|
||||||
{
|
{
|
||||||
"osmTags": "add-image>0",
|
"osmTags": "add-image>0",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Changeset added at least one image",
|
"en": "Changeset added at least one image"
|
||||||
"de": "Änderungssatz fügte mindestens ein Bild hinzu"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -667,8 +641,7 @@
|
||||||
{
|
{
|
||||||
"osmTags": "theme!=grb",
|
"osmTags": "theme!=grb",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Exclude GRB theme",
|
"en": "Exclude GRB theme"
|
||||||
"de": "GRB-Thema ausschließen"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -679,8 +652,7 @@
|
||||||
{
|
{
|
||||||
"osmTags": "theme!=etymology",
|
"osmTags": "theme!=etymology",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Exclude etymology theme",
|
"en": "Exclude etymology theme"
|
||||||
"de": "Etymologie-Thema ausschließen"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -695,8 +667,7 @@
|
||||||
{
|
{
|
||||||
"id": "link_to_more",
|
"id": "link_to_more",
|
||||||
"render": {
|
"render": {
|
||||||
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
|
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>"
|
||||||
"de": "Weitere Statistiken findest du <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -730,4 +701,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -166,7 +166,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"allowMove": false
|
"allowMove": false
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "town_hall",
|
"id": "town_hall",
|
||||||
|
|
|
@ -370,7 +370,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"allowMove": false
|
"allowMove": false
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -686,7 +686,6 @@
|
||||||
"enableImproveAccuraccy": true,
|
"enableImproveAccuraccy": true,
|
||||||
"enableRelocation": false
|
"enableRelocation": false
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"named_streets"
|
"named_streets"
|
||||||
],
|
],
|
||||||
|
|
|
@ -643,7 +643,8 @@
|
||||||
"pointIsMoved": "The point has been moved",
|
"pointIsMoved": "The point has been moved",
|
||||||
"reasons": {
|
"reasons": {
|
||||||
"reasonInaccurate": "The location is inaccurate by a few meter",
|
"reasonInaccurate": "The location is inaccurate by a few meter",
|
||||||
"reasonRelocation": "The object has been relocated to a totally different location"
|
"reasonRelocation": "The object has been relocated to a totally different location",
|
||||||
|
"reasonSnapTo": "This should be snapped onto {name}"
|
||||||
},
|
},
|
||||||
"selectReason": "Why do you move this object?",
|
"selectReason": "Why do you move this object?",
|
||||||
"whyMove": "Why do you want to move this point?",
|
"whyMove": "Why do you want to move this point?",
|
||||||
|
|
|
@ -12002,6 +12002,7 @@
|
||||||
},
|
},
|
||||||
"walls_and_buildings": {
|
"walls_and_buildings": {
|
||||||
"description": "Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user.",
|
"description": "Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user.",
|
||||||
|
"snapName": "a wall or building",
|
||||||
"tagRenderings": {
|
"tagRenderings": {
|
||||||
"entrance_info": {
|
"entrance_info": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
|
|
|
@ -9671,6 +9671,7 @@
|
||||||
},
|
},
|
||||||
"walls_and_buildings": {
|
"walls_and_buildings": {
|
||||||
"description": "Speciale ingebouwde laag voor alle muren en gebouwen. Deze laag is nuttig in voorkeuzen voor objecten die tegen muren geplaatst kunnen worden (bv. AEDs, brievenbussen, ingangen, adressen, beveiligingscamera's,…). Deze laag is standaard onzichtbaar en niet in te schakelen door de gebruiker.",
|
"description": "Speciale ingebouwde laag voor alle muren en gebouwen. Deze laag is nuttig in voorkeuzen voor objecten die tegen muren geplaatst kunnen worden (bv. AEDs, brievenbussen, ingangen, adressen, beveiligingscamera's,…). Deze laag is standaard onzichtbaar en niet in te schakelen door de gebruiker.",
|
||||||
|
"snapName": "een muur of gebouw",
|
||||||
"tagRenderings": {
|
"tagRenderings": {
|
||||||
"entrance_info": {
|
"entrance_info": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import { ChangeDescription } from "./ChangeDescription"
|
import { ChangeDescription } from "./ChangeDescription"
|
||||||
import OsmChangeAction from "./OsmChangeAction"
|
import OsmChangeAction from "./OsmChangeAction"
|
||||||
|
import { WayId } from "../../../Models/OsmFeature"
|
||||||
|
import InsertPointIntoWayAction from "./InsertPointIntoWayAction"
|
||||||
|
import { SpecialVisualizationState } from "../../../UI/SpecialVisualization"
|
||||||
|
|
||||||
export default class ChangeLocationAction extends OsmChangeAction {
|
export default class ChangeLocationAction extends OsmChangeAction {
|
||||||
private readonly _id: number
|
private readonly _id: number
|
||||||
private readonly _newLonLat: [number, number]
|
private readonly _newLonLat: [number, number]
|
||||||
private readonly _meta: { theme: string; reason: string }
|
private readonly _meta: { theme: string; reason: string }
|
||||||
|
private readonly state: SpecialVisualizationState
|
||||||
|
private snapTo: WayId | undefined
|
||||||
static metatags: {
|
static metatags: {
|
||||||
readonly key?: string
|
readonly key?: string
|
||||||
readonly value?: string
|
readonly value?: string
|
||||||
|
@ -21,28 +26,30 @@ export default class ChangeLocationAction extends OsmChangeAction {
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
id: string,
|
id: string,
|
||||||
newLonLat: [number, number],
|
newLonLat: [number, number],
|
||||||
|
snapTo: WayId | undefined,
|
||||||
meta: {
|
meta: {
|
||||||
theme: string
|
theme: string
|
||||||
reason: string
|
reason: string
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
super(id, true)
|
super(id, true)
|
||||||
|
this.state = state
|
||||||
if (!id.startsWith("node/")) {
|
if (!id.startsWith("node/")) {
|
||||||
throw "Invalid ID: only 'node/number' is accepted"
|
throw "Invalid ID: only 'node/number' is accepted"
|
||||||
}
|
}
|
||||||
this._id = Number(id.substring("node/".length))
|
this._id = Number(id.substring("node/".length))
|
||||||
this._newLonLat = newLonLat
|
this._newLonLat = newLonLat
|
||||||
|
this.snapTo = snapTo
|
||||||
this._meta = meta
|
this._meta = meta
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async CreateChangeDescriptions(): Promise<ChangeDescription[]> {
|
protected async CreateChangeDescriptions(): Promise<ChangeDescription[]> {
|
||||||
|
const [lon, lat] = this._newLonLat
|
||||||
const d: ChangeDescription = {
|
const d: ChangeDescription = {
|
||||||
changes: {
|
changes: { lon, lat },
|
||||||
lat: this._newLonLat[1],
|
|
||||||
lon: this._newLonLat[0],
|
|
||||||
},
|
|
||||||
type: "node",
|
type: "node",
|
||||||
id: this._id,
|
id: this._id,
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -51,7 +58,21 @@ export default class ChangeLocationAction extends OsmChangeAction {
|
||||||
specialMotivation: this._meta.reason,
|
specialMotivation: this._meta.reason,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if (!this.snapTo) {
|
||||||
|
return [d]
|
||||||
|
}
|
||||||
|
const snapToWay = await this.state.osmObjectDownloader.DownloadObjectAsync(this.snapTo, 0)
|
||||||
|
if (snapToWay === "deleted") {
|
||||||
|
return [d]
|
||||||
|
}
|
||||||
|
|
||||||
return [d]
|
const insertIntoWay = new InsertPointIntoWayAction(
|
||||||
|
lat, lon, this._id, snapToWay, {
|
||||||
|
allowReuseOfPreviouslyCreatedPoints: false,
|
||||||
|
reusePointWithinMeters: 0.25,
|
||||||
|
},
|
||||||
|
).prepareChangeDescription()
|
||||||
|
|
||||||
|
return [d, { ...insertIntoWay, meta: d.meta }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ChangeDescription } from "./ChangeDescription"
|
||||||
import { And } from "../../Tags/And"
|
import { And } from "../../Tags/And"
|
||||||
import { OsmWay } from "../OsmObject"
|
import { OsmWay } from "../OsmObject"
|
||||||
import { GeoOperations } from "../../GeoOperations"
|
import { GeoOperations } from "../../GeoOperations"
|
||||||
|
import InsertPointIntoWayAction from "./InsertPointIntoWayAction"
|
||||||
|
|
||||||
export default class CreateNewNodeAction extends OsmCreateAction {
|
export default class CreateNewNodeAction extends OsmCreateAction {
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +38,7 @@ export default class CreateNewNodeAction extends OsmCreateAction {
|
||||||
theme: string
|
theme: string
|
||||||
changeType: "create" | "import" | null
|
changeType: "create" | "import" | null
|
||||||
specialMotivation?: string
|
specialMotivation?: string
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
super(null, basicTags !== undefined && basicTags.length > 0)
|
super(null, basicTags !== undefined && basicTags.length > 0)
|
||||||
this._basicTags = basicTags
|
this._basicTags = basicTags
|
||||||
|
@ -101,72 +102,20 @@ export default class CreateNewNodeAction extends OsmCreateAction {
|
||||||
return [newPointChange]
|
return [newPointChange]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project the point onto the way
|
const change = new InsertPointIntoWayAction(
|
||||||
console.log("Snapping a node onto an existing way...")
|
|
||||||
const geojson = this._snapOnto.asGeoJson()
|
|
||||||
const projected = GeoOperations.nearestPoint(GeoOperations.outerRing(geojson), [
|
|
||||||
this._lon,
|
|
||||||
this._lat,
|
this._lat,
|
||||||
])
|
this._lon,
|
||||||
const projectedCoor = <[number, number]>projected.geometry.coordinates
|
id,
|
||||||
const index = projected.properties.index
|
this._snapOnto,
|
||||||
console.log("Attempting to snap:", { geojson, projected, projectedCoor, index })
|
{
|
||||||
// We check that it isn't close to an already existing point
|
reusePointWithinMeters: this._reusePointDistance,
|
||||||
let reusedPointId = undefined
|
allowReuseOfPreviouslyCreatedPoints: this._reusePreviouslyCreatedPoint,
|
||||||
let reusedPointCoordinates: [number, number] = undefined
|
},
|
||||||
let outerring: [number, number][]
|
).prepareChangeDescription()
|
||||||
|
|
||||||
if (geojson.geometry.type === "LineString") {
|
|
||||||
outerring = <[number, number][]>geojson.geometry.coordinates
|
|
||||||
} else if (geojson.geometry.type === "Polygon") {
|
|
||||||
outerring = <[number, number][]>geojson.geometry.coordinates[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = outerring[index]
|
|
||||||
if (GeoOperations.distanceBetween(prev, projectedCoor) < this._reusePointDistance) {
|
|
||||||
// We reuse this point instead!
|
|
||||||
reusedPointId = this._snapOnto.nodes[index]
|
|
||||||
reusedPointCoordinates = this._snapOnto.coordinates[index]
|
|
||||||
}
|
|
||||||
const next = outerring[index + 1]
|
|
||||||
if (GeoOperations.distanceBetween(next, projectedCoor) < this._reusePointDistance) {
|
|
||||||
// We reuse this point instead!
|
|
||||||
reusedPointId = this._snapOnto.nodes[index + 1]
|
|
||||||
reusedPointCoordinates = this._snapOnto.coordinates[index + 1]
|
|
||||||
}
|
|
||||||
if (reusedPointId !== undefined) {
|
|
||||||
this.setElementId(reusedPointId)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
tags: new And(this._basicTags).asChange(properties),
|
|
||||||
type: "node",
|
|
||||||
id: reusedPointId,
|
|
||||||
meta: this.meta,
|
|
||||||
changes: { lat: reusedPointCoordinates[0], lon: reusedPointCoordinates[1] },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const locations = [
|
|
||||||
...this._snapOnto.coordinates?.map(([lat, lon]) => <[number, number]>[lon, lat]),
|
|
||||||
]
|
|
||||||
const ids = [...this._snapOnto.nodes]
|
|
||||||
|
|
||||||
locations.splice(index + 1, 0, [this._lon, this._lat])
|
|
||||||
ids.splice(index + 1, 0, id)
|
|
||||||
|
|
||||||
// Allright, we have to insert a new point in the way
|
|
||||||
return [
|
return [
|
||||||
newPointChange,
|
newPointChange,
|
||||||
{
|
{ ...change, meta: this.meta },
|
||||||
type: "way",
|
|
||||||
id: this._snapOnto.id,
|
|
||||||
changes: {
|
|
||||||
coordinates: locations,
|
|
||||||
nodes: ids,
|
|
||||||
},
|
|
||||||
meta: this.meta,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
96
src/Logic/Osm/Actions/InsertPointIntoWayAction.ts
Normal file
96
src/Logic/Osm/Actions/InsertPointIntoWayAction.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import { ChangeDescription } from "./ChangeDescription"
|
||||||
|
import { GeoOperations } from "../../GeoOperations"
|
||||||
|
import { OsmWay } from "../OsmObject"
|
||||||
|
|
||||||
|
export default class InsertPointIntoWayAction {
|
||||||
|
private readonly _lat: number
|
||||||
|
private readonly _lon: number
|
||||||
|
private readonly _idToInsert: number
|
||||||
|
private readonly _snapOnto: OsmWay
|
||||||
|
private readonly _options: {
|
||||||
|
allowReuseOfPreviouslyCreatedPoints?: boolean
|
||||||
|
reusePointWithinMeters?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
lat: number,
|
||||||
|
lon: number,
|
||||||
|
idToInsert: number,
|
||||||
|
snapOnto: OsmWay,
|
||||||
|
options: {
|
||||||
|
allowReuseOfPreviouslyCreatedPoints?: boolean
|
||||||
|
reusePointWithinMeters?: number
|
||||||
|
}
|
||||||
|
){
|
||||||
|
this._lat = lat
|
||||||
|
this._lon = lon
|
||||||
|
this._idToInsert = idToInsert
|
||||||
|
this._snapOnto = snapOnto
|
||||||
|
this._options = options
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to create the changedescription of the way where the point is inserted
|
||||||
|
* Returns `undefined` if inserting failed
|
||||||
|
*/
|
||||||
|
public prepareChangeDescription(): Omit<ChangeDescription, "meta"> | undefined {
|
||||||
|
|
||||||
|
|
||||||
|
// Project the point onto the way
|
||||||
|
console.log("Snapping a node onto an existing way...")
|
||||||
|
const geojson = this._snapOnto.asGeoJson()
|
||||||
|
const projected = GeoOperations.nearestPoint(GeoOperations.outerRing(geojson), [
|
||||||
|
this._lon,
|
||||||
|
this._lat,
|
||||||
|
])
|
||||||
|
const projectedCoor = <[number, number]>projected.geometry.coordinates
|
||||||
|
const index = projected.properties.index
|
||||||
|
console.log("Attempting to snap:", { geojson, projected, projectedCoor, index })
|
||||||
|
// We check that it isn't close to an already existing point
|
||||||
|
let reusedPointId = undefined
|
||||||
|
let reusedPointCoordinates: [number, number] = undefined
|
||||||
|
let outerring: [number, number][]
|
||||||
|
|
||||||
|
if (geojson.geometry.type === "LineString") {
|
||||||
|
outerring = <[number, number][]>geojson.geometry.coordinates
|
||||||
|
} else if (geojson.geometry.type === "Polygon") {
|
||||||
|
outerring = <[number, number][]>geojson.geometry.coordinates[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const prev = outerring[index]
|
||||||
|
if (GeoOperations.distanceBetween(prev, projectedCoor) < this._options.reusePointWithinMeters) {
|
||||||
|
// We reuse this point instead!
|
||||||
|
reusedPointId = this._snapOnto.nodes[index]
|
||||||
|
reusedPointCoordinates = this._snapOnto.coordinates[index]
|
||||||
|
}
|
||||||
|
const next = outerring[index + 1]
|
||||||
|
if (GeoOperations.distanceBetween(next, projectedCoor) < this._options.reusePointWithinMeters) {
|
||||||
|
// We reuse this point instead!
|
||||||
|
reusedPointId = this._snapOnto.nodes[index + 1]
|
||||||
|
reusedPointCoordinates = this._snapOnto.coordinates[index + 1]
|
||||||
|
}
|
||||||
|
if (reusedPointId !== undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = [
|
||||||
|
...this._snapOnto.coordinates?.map(([lat, lon]) => <[number, number]>[lon, lat]),
|
||||||
|
]
|
||||||
|
const ids = [...this._snapOnto.nodes]
|
||||||
|
|
||||||
|
locations.splice(index + 1, 0, [this._lon, this._lat])
|
||||||
|
ids.splice(index + 1, 0, this._idToInsert)
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "way",
|
||||||
|
id: this._snapOnto.id,
|
||||||
|
changes: {
|
||||||
|
coordinates: locations,
|
||||||
|
nodes: ids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
super(
|
super(
|
||||||
"Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form. Note that 'tagRenderings+' will be inserted before 'leftover-questions'",
|
"Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form. Note that 'tagRenderings+' will be inserted before 'leftover-questions'",
|
||||||
[],
|
[],
|
||||||
"SubstituteLayer"
|
"SubstituteLayer",
|
||||||
)
|
)
|
||||||
this._state = state
|
this._state = state
|
||||||
}
|
}
|
||||||
|
@ -86,14 +86,14 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
(found["tagRenderings"] ?? []).length > 0
|
(found["tagRenderings"] ?? []).length > 0
|
||||||
) {
|
) {
|
||||||
context.err(
|
context.err(
|
||||||
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
|
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const trPlus = json["override"]["tagRenderings+"]
|
const trPlus = json["override"]["tagRenderings+"]
|
||||||
if (trPlus) {
|
if (trPlus) {
|
||||||
let index = found.tagRenderings.findIndex(
|
let index = found.tagRenderings.findIndex(
|
||||||
(tr) => tr["id"] === "leftover-questions"
|
(tr) => tr["id"] === "leftover-questions",
|
||||||
)
|
)
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = found.tagRenderings.length
|
index = found.tagRenderings.length
|
||||||
|
@ -107,8 +107,8 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.err(
|
context.err(
|
||||||
`Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
|
`Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
|
||||||
json["override"]
|
json["override"],
|
||||||
)}`
|
)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,9 +132,9 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
usedLabels.add(labels[forbiddenLabel])
|
usedLabels.add(labels[forbiddenLabel])
|
||||||
context.info(
|
context.info(
|
||||||
"Dropping tagRendering " +
|
"Dropping tagRendering " +
|
||||||
tr["id"] +
|
tr["id"] +
|
||||||
" as it has a forbidden label: " +
|
" as it has a forbidden label: " +
|
||||||
labels[forbiddenLabel]
|
labels[forbiddenLabel],
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
if (hideLabels.has(tr["id"])) {
|
if (hideLabels.has(tr["id"])) {
|
||||||
usedLabels.add(tr["id"])
|
usedLabels.add(tr["id"])
|
||||||
context.info(
|
context.info(
|
||||||
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label"
|
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label",
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -152,10 +152,10 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
usedLabels.add(tr["group"])
|
usedLabels.add(tr["group"])
|
||||||
context.info(
|
context.info(
|
||||||
"Dropping tagRendering " +
|
"Dropping tagRendering " +
|
||||||
tr["id"] +
|
tr["id"] +
|
||||||
" as its group `" +
|
" as its group `" +
|
||||||
tr["group"] +
|
tr["group"] +
|
||||||
"` is a forbidden label"
|
"` is a forbidden label",
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -166,8 +166,8 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
||||||
if (unused.length > 0) {
|
if (unused.length > 0) {
|
||||||
context.err(
|
context.err(
|
||||||
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
|
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
|
||||||
unused.join(", ") +
|
unused.join(", ") +
|
||||||
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
|
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
found.tagRenderings = filtered
|
found.tagRenderings = filtered
|
||||||
|
@ -184,7 +184,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
||||||
super(
|
super(
|
||||||
"Adds the default layers, namely: " + Constants.added_by_default.join(", "),
|
"Adds the default layers, namely: " + Constants.added_by_default.join(", "),
|
||||||
["layers"],
|
["layers"],
|
||||||
"AddDefaultLayers"
|
"AddDefaultLayers",
|
||||||
)
|
)
|
||||||
this._state = state
|
this._state = state
|
||||||
}
|
}
|
||||||
|
@ -207,10 +207,10 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
||||||
if (alreadyLoaded.has(v.id)) {
|
if (alreadyLoaded.has(v.id)) {
|
||||||
context.warn(
|
context.warn(
|
||||||
"Layout " +
|
"Layout " +
|
||||||
context +
|
context +
|
||||||
" already has a layer with name " +
|
" already has a layer with name " +
|
||||||
v.id +
|
v.id +
|
||||||
"; skipping inclusion of this builtin layer"
|
"; skipping inclusion of this builtin layer",
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -226,14 +226,14 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
||||||
super(
|
super(
|
||||||
"For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)",
|
"For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)",
|
||||||
["layers"],
|
["layers"],
|
||||||
"AddImportLayers"
|
"AddImportLayers",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||||
if (!(json.enableNoteImports ?? true)) {
|
if (!(json.enableNoteImports ?? true)) {
|
||||||
context.info(
|
context.info(
|
||||||
"Not creating a note import layers for theme " + json.id + " as they are disabled"
|
"Not creating a note import layers for theme " + json.id + " as they are disabled",
|
||||||
)
|
)
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
@ -268,7 +268,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
||||||
try {
|
try {
|
||||||
const importLayerResult = creator.convert(
|
const importLayerResult = creator.convert(
|
||||||
layer,
|
layer,
|
||||||
context.inOperation(this.name).enter(i1)
|
context.inOperation(this.name).enter(i1),
|
||||||
)
|
)
|
||||||
if (importLayerResult !== undefined) {
|
if (importLayerResult !== undefined) {
|
||||||
json.layers.push(importLayerResult)
|
json.layers.push(importLayerResult)
|
||||||
|
@ -288,7 +288,7 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
|
||||||
super(
|
super(
|
||||||
"Adds context to translations, including the prefix 'themes:json.id'; this is to make sure terms in an 'overrides' or inline layer are linkable too",
|
"Adds context to translations, including the prefix 'themes:json.id'; this is to make sure terms in an 'overrides' or inline layer are linkable too",
|
||||||
["_context"],
|
["_context"],
|
||||||
"AddContextToTranlationsInLayout"
|
"AddContextToTranlationsInLayout",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
|
||||||
// The context is used to generate the 'context' in the translation .It _must_ be `json.id` to correctly link into weblate
|
// The context is used to generate the 'context' in the translation .It _must_ be `json.id` to correctly link into weblate
|
||||||
return conversion.convert(
|
return conversion.convert(
|
||||||
json,
|
json,
|
||||||
ConversionContext.construct([json.id], ["AddContextToTranslation"])
|
ConversionContext.construct([json.id], ["AddContextToTranslation"]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
||||||
super(
|
super(
|
||||||
"Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards",
|
"Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards",
|
||||||
["overrideAll", "layers"],
|
["overrideAll", "layers"],
|
||||||
"ApplyOverrideAll"
|
"ApplyOverrideAll",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
||||||
layer.tagRenderings = tagRenderingsPlus
|
layer.tagRenderings = tagRenderingsPlus
|
||||||
} else {
|
} else {
|
||||||
let index = layer.tagRenderings.findIndex(
|
let index = layer.tagRenderings.findIndex(
|
||||||
(tr) => tr["id"] === "leftover-questions"
|
(tr) => tr["id"] === "leftover-questions",
|
||||||
)
|
)
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = layer.tagRenderings.length - 1
|
index = layer.tagRenderings.length - 1
|
||||||
|
@ -357,14 +357,9 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
|
|
||||||
constructor(state: DesugaringContext) {
|
constructor(state: DesugaringContext) {
|
||||||
super(
|
super(
|
||||||
`If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically)
|
`If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically)`,
|
||||||
|
|
||||||
Note that these layers are added _at the start_ of the layer list, meaning that they will see _every_ feature.
|
|
||||||
Furthermore, \`passAllFeatures\` will be set, so that they won't steal away features from further layers.
|
|
||||||
Some layers (e.g. \`all_buildings_and_walls\' or \'streets_with_a_name\') are invisible, so by default, \'force_load\' is set too.
|
|
||||||
`,
|
|
||||||
["layers"],
|
["layers"],
|
||||||
"AddDependencyLayersToTheme"
|
"AddDependencyLayersToTheme",
|
||||||
)
|
)
|
||||||
this._state = state
|
this._state = state
|
||||||
}
|
}
|
||||||
|
@ -373,7 +368,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
alreadyLoaded: LayerConfigJson[],
|
alreadyLoaded: LayerConfigJson[],
|
||||||
allKnownLayers: Map<string, LayerConfigJson>,
|
allKnownLayers: Map<string, LayerConfigJson>,
|
||||||
themeId: string,
|
themeId: string,
|
||||||
context: ConversionContext
|
context: ConversionContext,
|
||||||
): { config: LayerConfigJson; reason: string }[] {
|
): { config: LayerConfigJson; reason: string }[] {
|
||||||
const dependenciesToAdd: { config: LayerConfigJson; reason: string }[] = []
|
const dependenciesToAdd: { config: LayerConfigJson; reason: string }[] = []
|
||||||
const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map((l) => l?.id))
|
const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map((l) => l?.id))
|
||||||
|
@ -391,12 +386,13 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
reason: string
|
reason: string
|
||||||
context?: string
|
context?: string
|
||||||
neededBy: string
|
neededBy: string
|
||||||
|
checkHasSnapName: boolean
|
||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
for (const layerConfig of alreadyLoaded) {
|
for (const layerConfig of alreadyLoaded) {
|
||||||
try {
|
try {
|
||||||
const layerDeps = DependencyCalculator.getLayerDependencies(
|
const layerDeps = DependencyCalculator.getLayerDependencies(
|
||||||
new LayerConfig(layerConfig, themeId + "(dependencies)")
|
new LayerConfig(layerConfig, themeId + "(dependencies)"),
|
||||||
)
|
)
|
||||||
dependencies.push(...layerDeps)
|
dependencies.push(...layerDeps)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -413,7 +409,11 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
for (const dependency of dependencies) {
|
for (const dependency of dependencies) {
|
||||||
if (loadedLayerIds.has(dependency.neededLayer)) {
|
if (loadedLayerIds.has(dependency.neededLayer)) {
|
||||||
// We mark the needed layer as 'mustLoad'
|
// We mark the needed layer as 'mustLoad'
|
||||||
alreadyLoaded.find((l) => l.id === dependency.neededLayer).forceLoad = true
|
const loadedLayer = alreadyLoaded.find((l) => l.id === dependency.neededLayer)
|
||||||
|
loadedLayer.forceLoad = true
|
||||||
|
if(dependency.checkHasSnapName && !loadedLayer.snapName){
|
||||||
|
context.enters("layer dependency").err("Layer "+dependency.neededLayer+" is loaded because "+dependency.reason+"; so it must specify a `snapName`. This is used in the sentence `move this point to snap it to {snapName}`")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,10 +436,10 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
if (dep === undefined) {
|
if (dep === undefined) {
|
||||||
const message = [
|
const message = [
|
||||||
"Loading a dependency failed: layer " +
|
"Loading a dependency failed: layer " +
|
||||||
unmetDependency.neededLayer +
|
unmetDependency.neededLayer +
|
||||||
" is not found, neither as layer of " +
|
" is not found, neither as layer of " +
|
||||||
themeId +
|
themeId +
|
||||||
" nor as builtin layer.",
|
" nor as builtin layer.",
|
||||||
reason,
|
reason,
|
||||||
"Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","),
|
"Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","),
|
||||||
]
|
]
|
||||||
|
@ -455,11 +455,12 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
})
|
})
|
||||||
loadedLayerIds.add(dep.id)
|
loadedLayerIds.add(dep.id)
|
||||||
unmetDependencies = unmetDependencies.filter(
|
unmetDependencies = unmetDependencies.filter(
|
||||||
(d) => d.neededLayer !== unmetDependency.neededLayer
|
(d) => d.neededLayer !== unmetDependency.neededLayer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} while (unmetDependencies.length > 0)
|
} while (unmetDependencies.length > 0)
|
||||||
|
|
||||||
|
|
||||||
return dependenciesToAdd
|
return dependenciesToAdd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,12 +478,12 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
layers,
|
layers,
|
||||||
allKnownLayers,
|
allKnownLayers,
|
||||||
theme.id,
|
theme.id,
|
||||||
context
|
context,
|
||||||
)
|
)
|
||||||
if (dependencies.length > 0) {
|
if (dependencies.length > 0) {
|
||||||
for (const dependency of dependencies) {
|
for (const dependency of dependencies) {
|
||||||
context.info(
|
context.info(
|
||||||
"Added " + dependency.config.id + " to the theme. " + dependency.reason
|
"Added " + dependency.config.id + " to the theme. " + dependency.reason,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -530,7 +531,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
||||||
super(
|
super(
|
||||||
"Generates a warning if a theme uses an unsubstituted layer",
|
"Generates a warning if a theme uses an unsubstituted layer",
|
||||||
["layers"],
|
["layers"],
|
||||||
"WarnForUnsubstitutedLayersInTheme"
|
"WarnForUnsubstitutedLayersInTheme",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,7 +543,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
||||||
context
|
context
|
||||||
.enter("layers")
|
.enter("layers")
|
||||||
.err(
|
.err(
|
||||||
"No layers are defined. You must define at least one layer to have a valid theme"
|
"No layers are defined. You must define at least one layer to have a valid theme",
|
||||||
)
|
)
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
@ -566,10 +567,10 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
||||||
|
|
||||||
context.warn(
|
context.warn(
|
||||||
"The theme " +
|
"The theme " +
|
||||||
json.id +
|
json.id +
|
||||||
" has an inline layer: " +
|
" has an inline layer: " +
|
||||||
layer["id"] +
|
layer["id"] +
|
||||||
". This is discouraged."
|
". This is discouraged.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
|
@ -578,6 +579,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
||||||
|
|
||||||
class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
private readonly _state: DesugaringContext
|
private readonly _state: DesugaringContext
|
||||||
|
|
||||||
constructor(state: DesugaringContext) {
|
constructor(state: DesugaringContext) {
|
||||||
super("Various validation steps when everything is done", [], "PostvalidateTheme")
|
super("Various validation steps when everything is done", [], "PostvalidateTheme")
|
||||||
this._state = state
|
this._state = state
|
||||||
|
@ -596,13 +598,13 @@ class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
}
|
}
|
||||||
const sameBasedOn = <LayerConfigJson[]>(
|
const sameBasedOn = <LayerConfigJson[]>(
|
||||||
json.layers.filter(
|
json.layers.filter(
|
||||||
(l) => l["_basedOn"] === layer["_basedOn"] && l["id"] !== layer.id
|
(l) => l["_basedOn"] === layer["_basedOn"] && l["id"] !== layer.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const minZoomAll = Math.min(...sameBasedOn.map((sbo) => sbo.minzoom))
|
const minZoomAll = Math.min(...sameBasedOn.map((sbo) => sbo.minzoom))
|
||||||
|
|
||||||
const sameNameDetected = sameBasedOn.some(
|
const sameNameDetected = sameBasedOn.some(
|
||||||
(same) => JSON.stringify(layer["name"]) === JSON.stringify(same["name"])
|
(same) => JSON.stringify(layer["name"]) === JSON.stringify(same["name"]),
|
||||||
)
|
)
|
||||||
if (!sameNameDetected) {
|
if (!sameNameDetected) {
|
||||||
// The name is unique, so it'll won't be confusing
|
// The name is unique, so it'll won't be confusing
|
||||||
|
@ -611,12 +613,12 @@ class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
if (minZoomAll < layer.minzoom) {
|
if (minZoomAll < layer.minzoom) {
|
||||||
context.err(
|
context.err(
|
||||||
"There are multiple layers based on " +
|
"There are multiple layers based on " +
|
||||||
basedOn +
|
basedOn +
|
||||||
". The layer with id " +
|
". The layer with id " +
|
||||||
layer.id +
|
layer.id +
|
||||||
" has a minzoom of " +
|
" has a minzoom of " +
|
||||||
layer.minzoom +
|
layer.minzoom +
|
||||||
", and has a name set. Another similar layer has a lower minzoom. As such, the layer selection might show 'zoom in to see features' even though some of the features are already visible. Set `\"name\": null` for this layer and eventually remove the 'name':null for the other layer."
|
", and has a name set. Another similar layer has a lower minzoom. As such, the layer selection might show 'zoom in to see features' even though some of the features are already visible. Set `\"name\": null` for this layer and eventually remove the 'name':null for the other layer.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -636,17 +638,17 @@ class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
const closeLayers = Utils.sortedByLevenshteinDistance(
|
const closeLayers = Utils.sortedByLevenshteinDistance(
|
||||||
sameAs,
|
sameAs,
|
||||||
json.layers,
|
json.layers,
|
||||||
(l) => l["id"]
|
(l) => l["id"],
|
||||||
).map((l) => l["id"])
|
).map((l) => l["id"])
|
||||||
context
|
context
|
||||||
.enters("layers", config.id, "filter", "sameAs")
|
.enters("layers", config.id, "filter", "sameAs")
|
||||||
.err(
|
.err(
|
||||||
"The layer " +
|
"The layer " +
|
||||||
config.id +
|
config.id +
|
||||||
" follows the filter state of layer " +
|
" follows the filter state of layer " +
|
||||||
sameAs +
|
sameAs +
|
||||||
", but no layer with this name was found.\n\tDid you perhaps mean one of: " +
|
", but no layer with this name was found.\n\tDid you perhaps mean one of: " +
|
||||||
closeLayers.slice(0, 3).join(", ")
|
closeLayers.slice(0, 3).join(", "),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -654,6 +656,7 @@ class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
||||||
private state: DesugaringContext
|
private state: DesugaringContext
|
||||||
|
|
||||||
|
@ -661,7 +664,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
||||||
state: DesugaringContext,
|
state: DesugaringContext,
|
||||||
options?: {
|
options?: {
|
||||||
skipDefaultLayers: false | boolean
|
skipDefaultLayers: false | boolean
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
"Fully prepares and expands a theme",
|
"Fully prepares and expands a theme",
|
||||||
|
@ -683,7 +686,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
||||||
: new AddDefaultLayers(state),
|
: new AddDefaultLayers(state),
|
||||||
new AddDependencyLayersToTheme(state),
|
new AddDependencyLayersToTheme(state),
|
||||||
new AddImportLayers(),
|
new AddImportLayers(),
|
||||||
new PostvalidateTheme(state)
|
new PostvalidateTheme(state),
|
||||||
)
|
)
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
@ -698,13 +701,13 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
||||||
const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
|
const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
|
||||||
l.tagRenderings?.some((tr) =>
|
l.tagRenderings?.some((tr) =>
|
||||||
ValidationUtils.getSpecialVisualisations(<any>tr)?.some(
|
ValidationUtils.getSpecialVisualisations(<any>tr)?.some(
|
||||||
(special) => special.needsNodeDatabase
|
(special) => special.needsNodeDatabase,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
if (needsNodeDatabase) {
|
if (needsNodeDatabase) {
|
||||||
context.info(
|
context.info(
|
||||||
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
|
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes",
|
||||||
)
|
)
|
||||||
result.enableNodeDatabase = true
|
result.enableNodeDatabase = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ export default class DependencyCalculator {
|
||||||
*/
|
*/
|
||||||
public static getLayerDependencies(
|
public static getLayerDependencies(
|
||||||
layer: LayerConfig
|
layer: LayerConfig
|
||||||
): { neededLayer: string; reason: string; context?: string; neededBy: string }[] {
|
): { neededLayer: string; reason: string; context?: string; neededBy: string, checkHasSnapName: boolean }[] {
|
||||||
const deps: { neededLayer: string; reason: string; context?: string; neededBy: string }[] =
|
const deps: { neededLayer: string; reason: string; context?: string; neededBy: string, checkHasSnapName: boolean }[] =
|
||||||
[]
|
[]
|
||||||
|
|
||||||
for (let i = 0; layer.presets !== undefined && i < layer.presets.length; i++) {
|
for (let i = 0; layer.presets !== undefined && i < layer.presets.length; i++) {
|
||||||
|
@ -51,6 +51,7 @@ export default class DependencyCalculator {
|
||||||
reason: `preset \`${preset.title.textFor("en")}\` snaps to this layer`,
|
reason: `preset \`${preset.title.textFor("en")}\` snaps to this layer`,
|
||||||
context: `${layer.id}.presets[${i}]`,
|
context: `${layer.id}.presets[${i}]`,
|
||||||
neededBy: layer.id,
|
neededBy: layer.id,
|
||||||
|
checkHasSnapName: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -62,6 +63,7 @@ export default class DependencyCalculator {
|
||||||
reason: "a tagrendering needs this layer",
|
reason: "a tagrendering needs this layer",
|
||||||
context: tr.id,
|
context: tr.id,
|
||||||
neededBy: layer.id,
|
neededBy: layer.id,
|
||||||
|
checkHasSnapName: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,6 +99,7 @@ export default class DependencyCalculator {
|
||||||
"] which calculates the value for " +
|
"] which calculates the value for " +
|
||||||
currentKey,
|
currentKey,
|
||||||
neededBy: layer.id,
|
neededBy: layer.id,
|
||||||
|
checkHasSnapName: false
|
||||||
})
|
})
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -579,4 +579,13 @@ export interface LayerConfigJson {
|
||||||
* iftrue: Do not write 'change_within_x_m' and do not indicate that this was done by survey
|
* iftrue: Do not write 'change_within_x_m' and do not indicate that this was done by survey
|
||||||
*/
|
*/
|
||||||
enableMorePrivacy?: boolean
|
enableMorePrivacy?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* question: When a feature is snapped to this name, how should this item be called?
|
||||||
|
*
|
||||||
|
* In the move wizard, the option `snap object onto {snapName}` is shown
|
||||||
|
*
|
||||||
|
* group: hidden
|
||||||
|
*/
|
||||||
|
snapName?: Translatable
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { Overpass } from "../../Logic/Osm/Overpass"
|
||||||
import Constants from "../Constants"
|
import Constants from "../Constants"
|
||||||
import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
|
import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
|
||||||
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
||||||
|
import { And } from "../../Logic/Tags/And"
|
||||||
|
|
||||||
export default class LayerConfig extends WithContextLoader {
|
export default class LayerConfig extends WithContextLoader {
|
||||||
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
|
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
|
||||||
|
@ -48,6 +49,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
public readonly allowSplit: boolean
|
public readonly allowSplit: boolean
|
||||||
public readonly shownByDefault: boolean
|
public readonly shownByDefault: boolean
|
||||||
public readonly doCount: boolean
|
public readonly doCount: boolean
|
||||||
|
public readonly snapName?: Translation
|
||||||
/**
|
/**
|
||||||
* In seconds
|
* In seconds
|
||||||
*/
|
*/
|
||||||
|
@ -97,12 +99,13 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
mercatorCrs: json.source["mercatorCrs"],
|
mercatorCrs: json.source["mercatorCrs"],
|
||||||
idKey: json.source["idKey"],
|
idKey: json.source["idKey"],
|
||||||
},
|
},
|
||||||
json.id
|
json.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.allowSplit = json.allowSplit ?? false
|
this.allowSplit = json.allowSplit ?? false
|
||||||
this.name = Translations.T(json.name, translationContext + ".name")
|
this.name = Translations.T(json.name, translationContext + ".name")
|
||||||
|
this.snapName = Translations.T(json.snapName, translationContext + ".snapName")
|
||||||
|
|
||||||
if (json.description !== undefined) {
|
if (json.description !== undefined) {
|
||||||
if (Object.keys(json.description).length === 0) {
|
if (Object.keys(json.description).length === 0) {
|
||||||
|
@ -116,7 +119,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
if (json.calculatedTags !== undefined) {
|
if (json.calculatedTags !== undefined) {
|
||||||
if (!official) {
|
if (!official) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Unofficial theme ${this.id} with custom javascript! This is a security risk`
|
`Unofficial theme ${this.id} with custom javascript! This is a security risk`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.calculatedTags = []
|
this.calculatedTags = []
|
||||||
|
@ -186,7 +189,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
|
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
|
||||||
description: Translations.T(
|
description: Translations.T(
|
||||||
pr.description,
|
pr.description,
|
||||||
`${translationContext}.presets.${i}.description`
|
`${translationContext}.presets.${i}.description`,
|
||||||
),
|
),
|
||||||
preciseInput: preciseInput,
|
preciseInput: preciseInput,
|
||||||
exampleImages: pr.exampleImages,
|
exampleImages: pr.exampleImages,
|
||||||
|
@ -200,7 +203,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
|
|
||||||
if (json.lineRendering) {
|
if (json.lineRendering) {
|
||||||
this.lineRendering = Utils.NoNull(json.lineRendering).map(
|
this.lineRendering = Utils.NoNull(json.lineRendering).map(
|
||||||
(r, i) => new LineRenderingConfig(r, `${context}[${i}]`)
|
(r, i) => new LineRenderingConfig(r, `${context}[${i}]`),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.lineRendering = []
|
this.lineRendering = []
|
||||||
|
@ -208,7 +211,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
|
|
||||||
if (json.pointRendering) {
|
if (json.pointRendering) {
|
||||||
this.mapRendering = Utils.NoNull(json.pointRendering).map(
|
this.mapRendering = Utils.NoNull(json.pointRendering).map(
|
||||||
(r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`)
|
(r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.mapRendering = []
|
this.mapRendering = []
|
||||||
|
@ -220,7 +223,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
r.location.has("centroid") ||
|
r.location.has("centroid") ||
|
||||||
r.location.has("projected_centerpoint") ||
|
r.location.has("projected_centerpoint") ||
|
||||||
r.location.has("start") ||
|
r.location.has("start") ||
|
||||||
r.location.has("end")
|
r.location.has("end"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -242,7 +245,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
Constants.priviliged_layers.indexOf(<any>this.id) < 0 &&
|
Constants.priviliged_layers.indexOf(<any>this.id) < 0 &&
|
||||||
this.source !== null /*library layer*/ &&
|
this.source !== null /*library layer*/ &&
|
||||||
!this.source?.geojsonSource?.startsWith(
|
!this.source?.geojsonSource?.startsWith(
|
||||||
"https://api.openstreetmap.org/api/0.6/notes.json"
|
"https://api.openstreetmap.org/api/0.6/notes.json",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw (
|
throw (
|
||||||
|
@ -261,7 +264,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
typeof tr !== "string" &&
|
typeof tr !== "string" &&
|
||||||
tr["builtin"] === undefined &&
|
tr["builtin"] === undefined &&
|
||||||
tr["id"] === undefined &&
|
tr["id"] === undefined &&
|
||||||
tr["rewrite"] === undefined
|
tr["rewrite"] === undefined,
|
||||||
) ?? []
|
) ?? []
|
||||||
if (missingIds?.length > 0 && official) {
|
if (missingIds?.length > 0 && official) {
|
||||||
console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds)
|
console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds)
|
||||||
|
@ -272,8 +275,8 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
(tr, i) =>
|
(tr, i) =>
|
||||||
new TagRenderingConfig(
|
new TagRenderingConfig(
|
||||||
<QuestionableTagRenderingConfigJson>tr,
|
<QuestionableTagRenderingConfigJson>tr,
|
||||||
this.id + ".tagRenderings[" + i + "]"
|
this.id + ".tagRenderings[" + i + "]",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
if (json.units !== undefined && !Array.isArray(json.units)) {
|
if (json.units !== undefined && !Array.isArray(json.units)) {
|
||||||
throw (
|
throw (
|
||||||
|
@ -283,7 +286,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.units = (json.units ?? []).flatMap((unitJson, i) =>
|
this.units = (json.units ?? []).flatMap((unitJson, i) =>
|
||||||
Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`)
|
Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -359,7 +362,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
|
|
||||||
public GetBaseTags(): Record<string, string> {
|
public GetBaseTags(): Record<string, string> {
|
||||||
return TagUtils.changeAsProperties(
|
return TagUtils.changeAsProperties(
|
||||||
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }]
|
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +375,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
neededLayer: string
|
neededLayer: string
|
||||||
}[] = [],
|
}[] = [],
|
||||||
addedByDefault = false,
|
addedByDefault = false,
|
||||||
canBeIncluded = true
|
canBeIncluded = true,
|
||||||
): string {
|
): string {
|
||||||
const extraProps: string[] = []
|
const extraProps: string[] = []
|
||||||
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
|
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
|
||||||
|
@ -380,32 +383,32 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
if (canBeIncluded) {
|
if (canBeIncluded) {
|
||||||
if (addedByDefault) {
|
if (addedByDefault) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"**This layer is included automatically in every theme. This layer might contain no points**"
|
"**This layer is included automatically in every theme. This layer might contain no points**",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.shownByDefault === false) {
|
if (this.shownByDefault === false) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"This layer is not visible by default and must be enabled in the filter by the user. "
|
"This layer is not visible by default and must be enabled in the filter by the user. ",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.title === undefined) {
|
if (this.title === undefined) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable."
|
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.name === undefined && this.shownByDefault === false) {
|
if (this.name === undefined && this.shownByDefault === false) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true"
|
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.name === undefined) {
|
if (this.name === undefined) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`"
|
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.mapRendering.length === 0) {
|
if (this.mapRendering.length === 0) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`"
|
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,12 +418,12 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
"<img src='../warning.svg' height='1rem'/>",
|
"<img src='../warning.svg' height='1rem'/>",
|
||||||
"This layer is loaded from an external source, namely ",
|
"This layer is loaded from an external source, namely ",
|
||||||
"`" + this.source.geojsonSource + "`",
|
"`" + this.source.geojsonSource + "`",
|
||||||
].join("\n\n")
|
].join("\n\n"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data."
|
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +433,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
usingLayer = [
|
usingLayer = [
|
||||||
"## Themes using this layer",
|
"## Themes using this layer",
|
||||||
MarkdownUtils.list(
|
MarkdownUtils.list(
|
||||||
(usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`)
|
(usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
} else if (this.source !== null) {
|
} else if (this.source !== null) {
|
||||||
|
@ -446,15 +449,31 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
" into the layout as it depends on it: ",
|
" into the layout as it depends on it: ",
|
||||||
dep.reason,
|
dep.reason,
|
||||||
"(" + dep.context + ")",
|
"(" + dep.context + ")",
|
||||||
].join(" ")
|
].join(" "),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presets: string[] = []
|
||||||
|
if (this.presets.length > 0) {
|
||||||
|
|
||||||
|
presets = [
|
||||||
|
"## Presets",
|
||||||
|
"The following options to create new points are included:",
|
||||||
|
MarkdownUtils.list(this.presets.map(preset => {
|
||||||
|
let snaps = ""
|
||||||
|
if (preset.preciseInput?.snapToLayers) {
|
||||||
|
snaps = " (snaps to layers " + preset.preciseInput.snapToLayers.map(id => `\`${id}\``).join(", ") + ")"
|
||||||
|
}
|
||||||
|
return "**" + preset.title.txt + "** which has the following tags:" + new And(preset.tags).asHumanString(true) + snaps
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) {
|
for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join(
|
["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join(
|
||||||
" "
|
" ",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,10 +484,10 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
.filter((values) => values.key !== "id")
|
.filter((values) => values.key !== "id")
|
||||||
.map((values) => {
|
.map((values) => {
|
||||||
const embedded: string[] = values.values?.map((v) =>
|
const embedded: string[] = values.values?.map((v) =>
|
||||||
Link.OsmWiki(values.key, v, true).SetClass("mr-2").AsMarkdown()
|
Link.OsmWiki(values.key, v, true).SetClass("mr-2").AsMarkdown(),
|
||||||
) ?? ["_no preset options defined, or no values in them_"]
|
) ?? ["_no preset options defined, or no values in them_"]
|
||||||
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
|
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
|
||||||
values.key
|
values.key,
|
||||||
)}/`
|
)}/`
|
||||||
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
|
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
|
||||||
return [
|
return [
|
||||||
|
@ -483,7 +502,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
|
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
|
||||||
embedded.join(" "),
|
embedded.join(" "),
|
||||||
]
|
]
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
let quickOverview: string[] = []
|
let quickOverview: string[] = []
|
||||||
|
@ -493,7 +512,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
"this quick overview is incomplete",
|
"this quick overview is incomplete",
|
||||||
MarkdownUtils.table(
|
MarkdownUtils.table(
|
||||||
["attribute", "type", "values which are supported by this layer"],
|
["attribute", "type", "values which are supported by this layer"],
|
||||||
tableRows
|
tableRows,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -527,19 +546,19 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
const parts = neededTags["and"]
|
const parts = neededTags["and"]
|
||||||
tagsDescription.push(
|
tagsDescription.push(
|
||||||
"Elements must match **all** of the following expressions:",
|
"Elements must match **all** of the following expressions:",
|
||||||
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n")
|
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
|
||||||
)
|
)
|
||||||
} else if (neededTags["or"]) {
|
} else if (neededTags["or"]) {
|
||||||
const parts = neededTags["or"]
|
const parts = neededTags["or"]
|
||||||
tagsDescription.push(
|
tagsDescription.push(
|
||||||
"Elements must match **any** of the following expressions:",
|
"Elements must match **any** of the following expressions:",
|
||||||
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n")
|
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
tagsDescription.push(
|
tagsDescription.push(
|
||||||
"Elements must match the expression **" +
|
"Elements must match the expression **" +
|
||||||
neededTags.asHumanString(true, false, {}) +
|
neededTags.asHumanString(true, false, {}) +
|
||||||
"**"
|
"**",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,6 +578,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
].join("\n\n"),
|
].join("\n\n"),
|
||||||
MarkdownUtils.list(extraProps),
|
MarkdownUtils.list(extraProps),
|
||||||
...usingLayer,
|
...usingLayer,
|
||||||
|
...presets,
|
||||||
...tagsDescription,
|
...tagsDescription,
|
||||||
"## Supported attributes",
|
"## Supported attributes",
|
||||||
quickOverview,
|
quickOverview,
|
||||||
|
@ -583,4 +603,35 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
public isLeftRightSensitive(): boolean {
|
public isLeftRightSensitive(): boolean {
|
||||||
return this.lineRendering.some((lr) => lr.leftRightSensitive)
|
return this.lineRendering.some((lr) => lr.leftRightSensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getMostMatchingPreset(tags: Record<string, string>): PresetConfig {
|
||||||
|
const presets = this.presets
|
||||||
|
if (!presets) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const matchingPresets = presets
|
||||||
|
.filter((pr) => new And(pr.tags).matchesProperties(tags))
|
||||||
|
let mostShadowed = matchingPresets[0]
|
||||||
|
let mostShadowedTags = new And(mostShadowed.tags)
|
||||||
|
for (let i = 1; i < matchingPresets.length; i++) {
|
||||||
|
const pr = matchingPresets[i]
|
||||||
|
const prTags = new And(pr.tags)
|
||||||
|
if (mostShadowedTags.shadows(prTags)) {
|
||||||
|
if (!prTags.shadows(mostShadowedTags)) {
|
||||||
|
// We have a new most shadowed item
|
||||||
|
mostShadowed = pr
|
||||||
|
mostShadowedTags = prTags
|
||||||
|
} else {
|
||||||
|
// Both shadow each other: abort
|
||||||
|
mostShadowed = undefined
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (!prTags.shadows(mostShadowedTags)) {
|
||||||
|
// The new contender does not win, but it might defeat the current contender
|
||||||
|
mostShadowed = undefined
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mostShadowed ?? matchingPresets[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||||
import { Tag } from "../../Logic/Tags/Tag"
|
import { Tag } from "../../Logic/Tags/Tag"
|
||||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||||
|
import type { WayId } from "../../Models/OsmFeature"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An advanced location input, which has support to:
|
* An advanced location input, which has support to:
|
||||||
|
@ -45,11 +46,16 @@
|
||||||
}
|
}
|
||||||
export let snapToLayers: string[] | undefined = undefined
|
export let snapToLayers: string[] | undefined = undefined
|
||||||
export let targetLayer: LayerConfig | undefined = undefined
|
export let targetLayer: LayerConfig | undefined = undefined
|
||||||
|
/**
|
||||||
|
* If a 'targetLayer' is given, objects of this layer will be shown as well to avoid duplicates
|
||||||
|
* If you want to hide some of them, blacklist them here
|
||||||
|
*/
|
||||||
|
export let dontShow: string[] = []
|
||||||
export let maxSnapDistance: number = undefined
|
export let maxSnapDistance: number = undefined
|
||||||
export let presetProperties: Tag[] = []
|
export let presetProperties: Tag[] = []
|
||||||
let presetPropertiesUnpacked = TagUtils.KVtoProperties(presetProperties)
|
let presetPropertiesUnpacked = TagUtils.KVtoProperties(presetProperties)
|
||||||
|
|
||||||
export let snappedTo: UIEventSource<string | undefined>
|
export let snappedTo: UIEventSource<WayId | undefined>
|
||||||
|
|
||||||
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
||||||
lon: number
|
lon: number
|
||||||
|
@ -57,7 +63,7 @@
|
||||||
}>(undefined)
|
}>(undefined)
|
||||||
|
|
||||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||||
let initialMapProperties: Partial<MapProperties> & { location } = {
|
export let mapProperties: Partial<MapProperties> & { location } = {
|
||||||
zoom: new UIEventSource<number>(19),
|
zoom: new UIEventSource<number>(19),
|
||||||
maxbounds: new UIEventSource(undefined),
|
maxbounds: new UIEventSource(undefined),
|
||||||
/*If no snapping needed: the value is simply the map location;
|
/*If no snapping needed: the value is simply the map location;
|
||||||
|
@ -77,8 +83,11 @@
|
||||||
|
|
||||||
if (targetLayer) {
|
if (targetLayer) {
|
||||||
// Show already existing items
|
// Show already existing items
|
||||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
let featuresForLayer: FeatureSource = state.perLayer.get(targetLayer.id)
|
||||||
if (featuresForLayer) {
|
if (featuresForLayer) {
|
||||||
|
if (dontShow) {
|
||||||
|
featuresForLayer = new StaticFeatureSource(featuresForLayer.features.map(feats => feats.filter(f => dontShow.indexOf(f.properties.id) < 0)))
|
||||||
|
}
|
||||||
new ShowDataLayer(map, {
|
new ShowDataLayer(map, {
|
||||||
layer: targetLayer,
|
layer: targetLayer,
|
||||||
features: featuresForLayer,
|
features: featuresForLayer,
|
||||||
|
@ -104,13 +113,13 @@
|
||||||
const snappedLocation = new SnappingFeatureSource(
|
const snappedLocation = new SnappingFeatureSource(
|
||||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
||||||
// We snap to the (constantly updating) map location
|
// We snap to the (constantly updating) map location
|
||||||
initialMapProperties.location,
|
mapProperties.location,
|
||||||
{
|
{
|
||||||
maxDistance: maxSnapDistance ?? 15,
|
maxDistance: maxSnapDistance ?? 15,
|
||||||
allowUnsnapped: true,
|
allowUnsnapped: true,
|
||||||
snappedTo,
|
snappedTo,
|
||||||
snapLocation: value,
|
snapLocation: value,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
const withCorrectedAttributes = new StaticFeatureSource(
|
const withCorrectedAttributes = new StaticFeatureSource(
|
||||||
snappedLocation.features.mapD((feats) =>
|
snappedLocation.features.mapD((feats) =>
|
||||||
|
@ -124,8 +133,8 @@
|
||||||
...f,
|
...f,
|
||||||
properties,
|
properties,
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
// The actual point to be created, snapped at the new location
|
// The actual point to be created, snapped at the new location
|
||||||
new ShowDataLayer(map, {
|
new ShowDataLayer(map, {
|
||||||
|
@ -139,7 +148,7 @@
|
||||||
<LocationInput
|
<LocationInput
|
||||||
{map}
|
{map}
|
||||||
on:click
|
on:click
|
||||||
mapProperties={initialMapProperties}
|
{mapProperties}
|
||||||
value={preciseLocation}
|
value={preciseLocation}
|
||||||
initialCoordinate={coordinate}
|
initialCoordinate={coordinate}
|
||||||
maxDistanceInMeters={50}
|
maxDistanceInMeters={50}
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
import Relocation from "../../assets/svg/Relocation.svelte"
|
import Relocation from "../../assets/svg/Relocation.svelte"
|
||||||
import LockClosed from "@babeard/svelte-heroicons/solid/LockClosed"
|
import LockClosed from "@babeard/svelte-heroicons/solid/LockClosed"
|
||||||
import Key from "@babeard/svelte-heroicons/solid/Key"
|
import Key from "@babeard/svelte-heroicons/solid/Key"
|
||||||
|
import Snap from "../../assets/svg/Snap.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a single icon.
|
* Renders a single icon.
|
||||||
|
@ -152,6 +153,8 @@
|
||||||
<LockClosed class={clss} {color} />
|
<LockClosed class={clss} {color} />
|
||||||
{:else if icon === "key"}
|
{:else if icon === "key"}
|
||||||
<Key class={clss} {color} />
|
<Key class={clss} {color} />
|
||||||
|
{:else if icon === "snap"}
|
||||||
|
<Snap class={clss} />
|
||||||
{:else if Utils.isEmoji(icon)}
|
{:else if Utils.isEmoji(icon)}
|
||||||
<span style={`font-size: ${emojiHeight}; line-height: ${emojiHeight}`}>
|
<span style={`font-size: ${emojiHeight}; line-height: ${emojiHeight}`}>
|
||||||
{icon}
|
{icon}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
import type { MapProperties } from "../../Models/MapProperties"
|
import type { MapProperties } from "../../Models/MapProperties"
|
||||||
import type { Feature, Point } from "geojson"
|
import type { Feature, Point } from "geojson"
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
|
||||||
import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte"
|
import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||||
import Geosearch from "../BigComponents/Geosearch.svelte"
|
import Geosearch from "../BigComponents/Geosearch.svelte"
|
||||||
import If from "../Base/If.svelte"
|
import If from "../Base/If.svelte"
|
||||||
|
@ -21,6 +20,8 @@
|
||||||
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
|
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
|
||||||
import ThemeViewState from "../../Models/ThemeViewState"
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
import Icon from "../Map/Icon.svelte"
|
import Icon from "../Map/Icon.svelte"
|
||||||
|
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
|
||||||
|
import type { WayId } from "../../Models/OsmFeature"
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
|
|
||||||
|
@ -36,20 +37,22 @@
|
||||||
|
|
||||||
let newLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
|
let newLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
|
||||||
|
|
||||||
function initMapProperties() {
|
let snappedTo = new UIEventSource<WayId | undefined>(undefined)
|
||||||
|
|
||||||
|
function initMapProperties(reason: MoveReason) {
|
||||||
return <any>{
|
return <any>{
|
||||||
allowMoving: new UIEventSource(true),
|
allowMoving: new UIEventSource(true),
|
||||||
allowRotating: new UIEventSource(false),
|
allowRotating: new UIEventSource(false),
|
||||||
allowZooming: new UIEventSource(true),
|
allowZooming: new UIEventSource(true),
|
||||||
bounds: new UIEventSource(undefined),
|
bounds: new UIEventSource(undefined),
|
||||||
location: new UIEventSource({ lon, lat }),
|
location: new UIEventSource({ lon, lat }),
|
||||||
minzoom: new UIEventSource($reason.minZoom),
|
minzoom: new UIEventSource(reason.minZoom),
|
||||||
rasterLayer: state.mapProperties.rasterLayer,
|
rasterLayer: state.mapProperties.rasterLayer,
|
||||||
zoom: new UIEventSource($reason?.startZoom ?? 16),
|
zoom: new UIEventSource(reason?.startZoom ?? 16),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let moveWizardState = new MoveWizardState(id, layer.allowMove, state)
|
let moveWizardState = new MoveWizardState(id, layer.allowMove, layer, state)
|
||||||
if (moveWizardState.reasons.length === 1) {
|
if (moveWizardState.reasons.length === 1) {
|
||||||
reason.setData(moveWizardState.reasons[0])
|
reason.setData(moveWizardState.reasons[0])
|
||||||
}
|
}
|
||||||
|
@ -57,8 +60,8 @@
|
||||||
let currentMapProperties: MapProperties = undefined
|
let currentMapProperties: MapProperties = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoginToggle {state}>
|
{#if moveWizardState.reasons.length > 0}
|
||||||
{#if moveWizardState.reasons.length > 0}
|
<LoginToggle {state}>
|
||||||
{#if $notAllowed}
|
{#if $notAllowed}
|
||||||
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
|
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
|
||||||
<Move_not_allowed class="m-2 h-8 w-8" />
|
<Move_not_allowed class="m-2 h-8 w-8" />
|
||||||
|
@ -81,7 +84,7 @@
|
||||||
<span class="flex flex-col p-2">
|
<span class="flex flex-col p-2">
|
||||||
{#if currentStep === "reason" && moveWizardState.reasons.length > 1}
|
{#if currentStep === "reason" && moveWizardState.reasons.length > 1}
|
||||||
{#each moveWizardState.reasons as reasonSpec}
|
{#each moveWizardState.reasons as reasonSpec}
|
||||||
<button
|
<button class="flex justify-start"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
reason.setData(reasonSpec)
|
reason.setData(reasonSpec)
|
||||||
currentStep = "pick_location"
|
currentStep = "pick_location"
|
||||||
|
@ -93,10 +96,16 @@
|
||||||
{/each}
|
{/each}
|
||||||
{:else if currentStep === "pick_location" || currentStep === "reason"}
|
{:else if currentStep === "pick_location" || currentStep === "reason"}
|
||||||
<div class="relative h-64 w-full">
|
<div class="relative h-64 w-full">
|
||||||
<LocationInput
|
<NewPointLocationInput
|
||||||
mapProperties={(currentMapProperties = initMapProperties())}
|
mapProperties={(currentMapProperties = initMapProperties($reason))}
|
||||||
value={newLocation}
|
value={newLocation}
|
||||||
initialCoordinate={{ lon, lat }}
|
{state}
|
||||||
|
coordinate={{ lon, lat }}
|
||||||
|
{snappedTo}
|
||||||
|
maxSnapDistance={$reason.maxSnapDistance ?? 5}
|
||||||
|
snapToLayers={$reason.snapTo}
|
||||||
|
targetLayer={layer}
|
||||||
|
dontShow={[id]}
|
||||||
/>
|
/>
|
||||||
<div class="absolute bottom-0 left-0">
|
<div class="absolute bottom-0 left-0">
|
||||||
<OpenBackgroundSelectorButton {state} />
|
<OpenBackgroundSelectorButton {state} />
|
||||||
|
@ -116,7 +125,7 @@
|
||||||
<button
|
<button
|
||||||
class="primary w-full"
|
class="primary w-full"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
|
moveWizardState.moveFeature(newLocation.data, snappedTo.data, reason.data, featureToMove)
|
||||||
currentStep = "moved"
|
currentStep = "moved"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -155,5 +164,5 @@
|
||||||
</span>
|
</span>
|
||||||
</AccordionSingle>
|
</AccordionSingle>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</LoginToggle>
|
||||||
</LoginToggle>
|
{/if}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { Feature, Point } from "geojson"
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
import Relocation from "../../assets/svg/Relocation.svelte"
|
import Relocation from "../../assets/svg/Relocation.svelte"
|
||||||
import Location from "../../assets/svg/Location.svelte"
|
import Location from "../../assets/svg/Location.svelte"
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
|
import { WayId } from "../../Models/OsmFeature"
|
||||||
|
|
||||||
export interface MoveReason {
|
export interface MoveReason {
|
||||||
text: Translation | string
|
text: Translation | string
|
||||||
|
@ -24,25 +26,40 @@ export interface MoveReason {
|
||||||
startZoom: number
|
startZoom: number
|
||||||
minZoom: number
|
minZoom: number
|
||||||
eraseAddressFields: false | boolean
|
eraseAddressFields: false | boolean
|
||||||
|
/**
|
||||||
|
* Snap to these layers
|
||||||
|
*/
|
||||||
|
snapTo?: string[]
|
||||||
|
maxSnapDistance?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MoveWizardState {
|
export class MoveWizardState {
|
||||||
public readonly reasons: ReadonlyArray<MoveReason>
|
public readonly reasons: ReadonlyArray<MoveReason>
|
||||||
|
|
||||||
public readonly moveDisallowedReason = new UIEventSource<Translation>(undefined)
|
public readonly moveDisallowedReason = new UIEventSource<Translation>(undefined)
|
||||||
|
private readonly layer: LayerConfig
|
||||||
private readonly _state: SpecialVisualizationState
|
private readonly _state: SpecialVisualizationState
|
||||||
|
private readonly featureToMoveId: string
|
||||||
|
|
||||||
constructor(id: string, options: MoveConfig, state: SpecialVisualizationState) {
|
/**
|
||||||
|
* Initialize the movestate for the feature of the given ID
|
||||||
|
* @param id of the feature that should be moved
|
||||||
|
* @param options
|
||||||
|
* @param layer
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
constructor(id: string, options: MoveConfig, layer: LayerConfig, state: SpecialVisualizationState) {
|
||||||
|
this.layer = layer
|
||||||
this._state = state
|
this._state = state
|
||||||
this.reasons = MoveWizardState.initReasons(options)
|
this.featureToMoveId = id
|
||||||
|
this.reasons = this.initReasons(options)
|
||||||
if (this.reasons.length > 0) {
|
if (this.reasons.length > 0) {
|
||||||
this.checkIsAllowed(id)
|
this.checkIsAllowed(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initReasons(options: MoveConfig): MoveReason[] {
|
private initReasons(options: MoveConfig): MoveReason[] {
|
||||||
const t = Translations.t.move
|
const t = Translations.t.move
|
||||||
|
|
||||||
const reasons: MoveReason[] = []
|
const reasons: MoveReason[] = []
|
||||||
if (options.enableRelocation) {
|
if (options.enableRelocation) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
|
@ -72,20 +89,52 @@ export class MoveWizardState {
|
||||||
eraseAddressFields: false,
|
eraseAddressFields: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tags = this._state.featureProperties.getStore(this.featureToMoveId).data
|
||||||
|
const matchingPresets = this.layer.presets.filter(preset => preset.preciseInput.snapToLayers && new And(preset.tags).matchesProperties(tags))
|
||||||
|
const matchingPreset = matchingPresets.flatMap(pr => pr.preciseInput?.snapToLayers)
|
||||||
|
for (const layerId of matchingPreset) {
|
||||||
|
const snapOntoLayer = this._state.layout.getLayer(layerId)
|
||||||
|
const text = <Translation> t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer.snapName)
|
||||||
|
reasons.push({
|
||||||
|
text,
|
||||||
|
invitingText: text,
|
||||||
|
icon: "snap",
|
||||||
|
changesetCommentValue: "snap",
|
||||||
|
lockBounds: true,
|
||||||
|
includeSearch: false,
|
||||||
|
background: "photo",
|
||||||
|
startZoom: 19,
|
||||||
|
minZoom: 16,
|
||||||
|
eraseAddressFields: false,
|
||||||
|
snapTo: [snapOntoLayer.id],
|
||||||
|
maxSnapDistance: 5,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return reasons
|
return reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
public async moveFeature(
|
public async moveFeature(
|
||||||
loc: { lon: number; lat: number },
|
loc: { lon: number; lat: number },
|
||||||
|
snappedTo: WayId,
|
||||||
reason: MoveReason,
|
reason: MoveReason,
|
||||||
featureToMove: Feature<Point>
|
featureToMove: Feature<Point>,
|
||||||
) {
|
) {
|
||||||
const state = this._state
|
const state = this._state
|
||||||
|
if(snappedTo !== undefined){
|
||||||
|
this.moveDisallowedReason.set(Translations.t.move.partOfAWay)
|
||||||
|
}
|
||||||
await state.changes.applyAction(
|
await state.changes.applyAction(
|
||||||
new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
|
new ChangeLocationAction(state,
|
||||||
reason: reason.changesetCommentValue,
|
featureToMove.properties.id,
|
||||||
theme: state.layout.id,
|
[loc.lon, loc.lat],
|
||||||
})
|
snappedTo,
|
||||||
|
{
|
||||||
|
reason: reason.changesetCommentValue,
|
||||||
|
theme: state.layout.id,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
featureToMove.properties._lat = loc.lat
|
featureToMove.properties._lat = loc.lat
|
||||||
featureToMove.properties._lon = loc.lon
|
featureToMove.properties._lon = loc.lon
|
||||||
|
@ -104,8 +153,8 @@ export class MoveWizardState {
|
||||||
{
|
{
|
||||||
changeType: "relocated",
|
changeType: "relocated",
|
||||||
theme: state.layout.id,
|
theme: state.layout.id,
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1995,35 +1995,8 @@ export default class SpecialVisualizations {
|
||||||
layer: LayerConfig
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const translation = tagSource.map((tags) => {
|
const translation = tagSource.map((tags) => {
|
||||||
const presets = state.layout.getMatchingLayer(tags)?.presets
|
const layer = state.layout.getMatchingLayer(tags)
|
||||||
if(!presets){
|
return layer?.getMostMatchingPreset(tags)?.description
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const matchingPresets = presets
|
|
||||||
.filter((pr) => pr.description !== undefined)
|
|
||||||
.filter((pr) => new And(pr.tags).matchesProperties(tags))
|
|
||||||
let mostShadowed = matchingPresets[0]
|
|
||||||
let mostShadowedTags = new And(mostShadowed.tags)
|
|
||||||
for (let i = 1; i < matchingPresets.length; i++) {
|
|
||||||
const pr = matchingPresets[i]
|
|
||||||
const prTags = new And(pr.tags)
|
|
||||||
if (mostShadowedTags.shadows(prTags)) {
|
|
||||||
if (!prTags.shadows(mostShadowedTags)) {
|
|
||||||
// We have a new most shadowed item
|
|
||||||
mostShadowed = pr
|
|
||||||
mostShadowedTags = prTags
|
|
||||||
} else {
|
|
||||||
// Both shadow each other: abort
|
|
||||||
mostShadowed = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (!prTags.shadows(mostShadowedTags)) {
|
|
||||||
// The new contender does not win, but it might defeat the current contender
|
|
||||||
mostShadowed = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mostShadowed?.description ?? matchingPresets[0]?.description
|
|
||||||
})
|
})
|
||||||
return new VariableUiElement(translation)
|
return new VariableUiElement(translation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -417,6 +417,9 @@ export class TypedTranslation<T extends Record<string, any>> extends Translation
|
||||||
key: string,
|
key: string,
|
||||||
replaceWith: Translation
|
replaceWith: Translation
|
||||||
): TypedTranslation<Omit<T, K>> {
|
): TypedTranslation<Omit<T, K>> {
|
||||||
|
if(replaceWith === undefined){
|
||||||
|
return this
|
||||||
|
}
|
||||||
const newTranslations: Record<string, string> = {}
|
const newTranslations: Record<string, string> = {}
|
||||||
const toSearch = "{" + key + "}"
|
const toSearch = "{" + key + "}"
|
||||||
const missingLanguages = new Set<string>(Object.keys(this.translations))
|
const missingLanguages = new Set<string>(Object.keys(this.translations))
|
||||||
|
|
4
src/assets/svg/Snap.svelte
Normal file
4
src/assets/svg/Snap.svelte
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<script>
|
||||||
|
export let color = "#000000"
|
||||||
|
</script>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) --> <svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus width="120" height="120" viewBox="0 0 120 120" version="1.1" id="svg1" xml:space="preserve" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" sodipodi:docname="snap.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"><sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#999999" borderopacity="1" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showguides="true" inkscape:zoom="4.5168066" inkscape:cx="40.51535" inkscape:cy="42.39721" inkscape:window-width="1920" inkscape:window-height="995" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1"><sodipodi:guide position="315.49944,61.936443" orientation="0,-1" id="guide2" inkscape:locked="false" /></sodipodi:namedview><defs id="defs1" /><g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-5,-5)"><path id="path1-2" style="fill:#808080;fill-opacity:1;stroke-width:3.93092" d="m 72.294942,72.07284 c 0.948679,-0.909931 1.380066,-2.234124 1.148907,-3.527969 L 69.995854,51.284128 C 69.614243,49.146951 67.469637,47.282834 65.914649,48.798037 L 59.882054,54.677556 44.886208,39.828561 c -1.383257,-1.369713 -3.807955,-0.909932 -4.298111,-0.288099 -1.707154,2.165786 -0.138139,3.968458 0.177549,4.304093 l 14.45874,15.372473 -6.032604,5.879513 c -1.554665,1.51554 0.253754,3.707343 2.380393,4.143751 l 17.16644,3.89041 c 1.287478,0.264342 2.622313,-0.132882 3.556328,-1.057866 z" sodipodi:nodetypes="cccccsssccccc" /><g id="g8" /><g id="g10" transform="rotate(-175.99037,61.199753,60.156378)"><g id="path1" inkscape:transform-center-x="1.8238832" inkscape:transform-center-y="31.570993"><path style="color:{color};fill:{color};stroke-linecap:round;-inkscape-stroke:none" d="M 10,90 45,10" id="path3" /><path id="path4" style="color:{color};fill:{color}009;stroke-linecap:round;-inkscape-stroke:none" d="M 45.097656,5.0019531 A 5,5 0 0 0 43.177734,5.34375 5,5 0 0 0 40.419922,7.9960938 L 35.865234,18.40625 c 3.405007,0.669609 6.469474,2.331825 8.867188,4.679688 L 49.580078,12.003906 A 5,5 0 0 0 47.003906,5.4199219 5,5 0 0 0 45.097656,5.0019531 Z M 22.177734,49.691406 5.4199219,87.996094 a 5,5 0 0 0 2.5761719,6.583984 5,5 0 0 0 6.5839842,-2.576172 L 31.621094,53.052734 c -3.513941,-0.175553 -6.76611,-1.396873 -9.44336,-3.361328 z" /></g><path style="fill:{color};fill-opacity:1;stroke:{color};stroke-width:0;stroke-linecap:round;stroke-opacity:1" id="path9" sodipodi:type="arc" sodipodi:cx="32.616085" sodipodi:cy="35.55938" sodipodi:rx="12.741771" sodipodi:ry="12.741771" sodipodi:start="0" sodipodi:end="6.26046" sodipodi:open="true" sodipodi:arc-type="arc" d="M 45.357856,35.55938 A 12.741771,12.741771 0 0 1 32.688475,48.300945 12.741771,12.741771 0 0 1 19.875137,35.704157 12.741771,12.741771 0 0 1 32.398925,22.81946 12.741771,12.741771 0 0 1 45.354566,35.269844" /></g><path style="fill:#808080;fill-opacity:1;stroke:{color};stroke-width:0;stroke-linecap:round;stroke-opacity:1" id="path10" sodipodi:type="arc" sodipodi:cx="26.067774" sodipodi:cy="26.136267" sodipodi:rx="12.741771" sodipodi:ry="12.741771" sodipodi:start="0" sodipodi:end="6.26046" sodipodi:open="true" sodipodi:arc-type="arc" d="M 38.809545,26.136267 A 12.741771,12.741771 0 0 1 26.140164,38.877832 12.741771,12.741771 0 0 1 13.326826,26.281044 12.741771,12.741771 0 0 1 25.850614,13.396347 12.741771,12.741771 0 0 1 38.806255,25.846731" /></g></svg>
|
Loading…
Reference in a new issue