forked from MapComplete/MapComplete
Documentation updates
This commit is contained in:
parent
78c689b7e8
commit
09eee08fbc
32 changed files with 600 additions and 87 deletions
Customizations
Docs
BuiltinLayers.mdCalculatedTags.mdSpecialInputElements.mdSpecialRenderings.md
TagInfo
mapcomplete_cafes_and_pubs.jsonmapcomplete_cyclofix.jsonmapcomplete_food.jsonmapcomplete_fritures.jsonmapcomplete_toilets.json
URL_Parameters.mdLogic
Actors
FeatureSource
Osm
State
Models/ThemeConfig
UI
assets/layers
conflation
gps_location
home_location
left_right_style
type_node
scripts
|
@ -1,6 +1,13 @@
|
|||
import * as known_layers from "../assets/generated/known_layers_and_themes.json"
|
||||
import {Utils} from "../Utils";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import BaseUIElement from "../UI/BaseUIElement";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import Title from "../UI/Base/Title";
|
||||
import List from "../UI/Base/List";
|
||||
import {AllKnownLayouts} from "./AllKnownLayouts";
|
||||
import {isNullOrUndefined} from "util";
|
||||
import {Layer} from "leaflet";
|
||||
|
||||
export default class AllKnownLayers {
|
||||
|
||||
|
@ -9,6 +16,16 @@ export default class AllKnownLayers {
|
|||
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
|
||||
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
|
||||
|
||||
|
||||
public static added_by_default: string[] = ["gps_location", "home_location"]
|
||||
public static no_include: string[] = [ "conflation", "left_right_style"]
|
||||
/**
|
||||
* Layer IDs of layers which have special properties through built-in hooks
|
||||
*/
|
||||
public static priviliged_layers: string[] = [...AllKnownLayers.added_by_default, "type_node",...AllKnownLayers.no_include]
|
||||
|
||||
|
||||
|
||||
private static getSharedLayers(): Map<string, LayerConfig> {
|
||||
const sharedLayers = new Map<string, LayerConfig>();
|
||||
for (const layer of known_layers.layers) {
|
||||
|
@ -16,7 +33,6 @@ export default class AllKnownLayers {
|
|||
// @ts-ignore
|
||||
const parsed = new LayerConfig(layer, "shared_layers")
|
||||
sharedLayers.set(layer.id, parsed);
|
||||
sharedLayers[layer.id] = parsed;
|
||||
} catch (e) {
|
||||
if (!Utils.runningFromConsole) {
|
||||
console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e)
|
||||
|
@ -57,5 +73,4 @@ export default class AllKnownLayers {
|
|||
return sharedLayers;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@ import AllKnownLayers from "./AllKnownLayers";
|
|||
import * as known_themes from "../assets/generated/known_layers_and_themes.json"
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import BaseUIElement from "../UI/BaseUIElement";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import Title from "../UI/Base/Title";
|
||||
import List from "../UI/Base/List";
|
||||
|
||||
export class AllKnownLayouts {
|
||||
|
||||
|
@ -29,6 +33,50 @@ export class AllKnownLayouts {
|
|||
return allLayers
|
||||
}
|
||||
|
||||
public static GenLayerOverviewText(): BaseUIElement {
|
||||
for (const id of AllKnownLayers.priviliged_layers) {
|
||||
if (!AllKnownLayers.sharedLayers.has(id)) {
|
||||
throw "Priviliged layer definition not found: " + id
|
||||
}
|
||||
}
|
||||
const allLayers: LayerConfig[] = Array.from(AllKnownLayers.sharedLayers.values())
|
||||
|
||||
const themesPerLayer = new Map<string, string[]>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
if(layout.hideFromOverview){
|
||||
continue
|
||||
}
|
||||
for (const layer of layout.layers) {
|
||||
if (!themesPerLayer.has(layer.id)) {
|
||||
themesPerLayer.set(layer.id, [])
|
||||
}
|
||||
themesPerLayer.get(layer.id).push(layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let popularLayerCutoff = 2;
|
||||
const popupalLayers = allLayers.filter((layer) => themesPerLayer.get(layer.id)?.length >= 2)
|
||||
.filter(layer => AllKnownLayers.priviliged_layers.indexOf(layer.id) < 0)
|
||||
|
||||
return new Combine([
|
||||
new Title("Special and other useful layers", 1),
|
||||
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
|
||||
new Title("Priviliged layers", 1),
|
||||
new List(AllKnownLayers.priviliged_layers.map(id => "[" + id + "](#" + id + ")")),
|
||||
...AllKnownLayers.priviliged_layers
|
||||
.map(id => AllKnownLayers.sharedLayers.get(id))
|
||||
.map((l) => l.GenerateDocumentation(themesPerLayer.get(l.id), AllKnownLayers.added_by_default.indexOf(l.id) >= 0, AllKnownLayers.no_include.indexOf(l.id) >= 0)),
|
||||
new Title("Frequently reused layers", 1),
|
||||
"The following layers are used by at least "+popularLayerCutoff+" mapcomplete themes and might be interesting for your custom theme too",
|
||||
new List(popupalLayers.map(layer => "[" + layer.id + "](#" + layer.id + ")")),
|
||||
...popupalLayers.map((layer) => layer.GenerateDocumentation(themesPerLayer.get(layer.id)))
|
||||
])
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] {
|
||||
const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"]
|
||||
const list = []
|
||||
|
|
145
Docs/BuiltinLayers.md
Normal file
145
Docs/BuiltinLayers.md
Normal file
|
@ -0,0 +1,145 @@
|
|||
|
||||
|
||||
Special and other useful layers
|
||||
=================================
|
||||
|
||||
MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.
|
||||
|
||||
Priviliged layers
|
||||
===================
|
||||
|
||||
|
||||
|
||||
- [gps_location](#gps_location)
|
||||
- [home_location](#home_location)
|
||||
- [type_node](#type_node)
|
||||
- [conflation](#conflation)
|
||||
- [left_right_style](#left_right_style)
|
||||
|
||||
|
||||
### gps_location
|
||||
|
||||
**This layer is included automatically in every theme. This layer might contain no points** [Go to the source code](../assets/layers/gps_location/gps_location.json) Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) returned by the browser.
|
||||
|
||||
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
|
||||
|
||||
|
||||
### home_location
|
||||
|
||||
**This layer is included automatically in every theme. This layer might contain no points** [Go to the source code](../assets/layers/home_location/home_location.json) Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.
|
||||
|
||||
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
|
||||
|
||||
|
||||
### type_node
|
||||
|
||||
[Go to the source code](../assets/layers/type_node/type_node.json) This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.
|
||||
|
||||
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
|
||||
|
||||
|
||||
### conflation
|
||||
|
||||
[Go to the source code](../assets/layers/conflation/conflation.json) If the import-button is set to conflate two ways, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme.
|
||||
|
||||
|
||||
|
||||
|
||||
### left_right_style
|
||||
|
||||
[Go to the source code](../assets/layers/left_right_style/left_right_style.json) Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads. Cannot be included in a theme
|
||||
|
||||
- Not clickable by default. If you import this layer in your theme, override `title` to make this clickable
|
||||
- Not visible in the layer selection by default. If you want to make this layer toggable, override `name`
|
||||
- Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`
|
||||
|
||||
|
||||
Frequently reused layers
|
||||
==========================
|
||||
|
||||
The following layers are used by at least 2 mapcomplete themes and might be interesting for your custom theme too
|
||||
|
||||
- [bicycle_library](#bicycle_library)
|
||||
- [drinking_water](#drinking_water)
|
||||
- [food](#food)
|
||||
- [map](#map)
|
||||
- [all_streets](#all_streets)
|
||||
|
||||
|
||||
### bicycle_library
|
||||
|
||||
[Go to the source code](../assets/layers/bicycle_library/bicycle_library.json) A facility where bicycles can be lent for longer period of times
|
||||
|
||||
|
||||
|
||||
|
||||
#### Themes using this layer
|
||||
|
||||
|
||||
|
||||
- [bicyclelib](https://mapcomplete.osm.be/bicyclelib)
|
||||
- [cyclofix](https://mapcomplete.osm.be/cyclofix)
|
||||
|
||||
|
||||
### drinking_water
|
||||
|
||||
[Go to the source code](../assets/layers/drinking_water/drinking_water.json)
|
||||
|
||||
|
||||
|
||||
|
||||
#### Themes using this layer
|
||||
|
||||
|
||||
|
||||
- [cyclofix](https://mapcomplete.osm.be/cyclofix)
|
||||
- [drinking_water](https://mapcomplete.osm.be/drinking_water)
|
||||
- [nature](https://mapcomplete.osm.be/nature)
|
||||
|
||||
|
||||
### food
|
||||
|
||||
[Go to the source code](../assets/layers/food/food.json)
|
||||
|
||||
|
||||
|
||||
|
||||
#### Themes using this layer
|
||||
|
||||
|
||||
|
||||
- [food](https://mapcomplete.osm.be/food)
|
||||
- [fritures](https://mapcomplete.osm.be/fritures)
|
||||
|
||||
|
||||
### map
|
||||
|
||||
[Go to the source code](../assets/layers/map/map.json) A map, meant for tourists which is permanently installed in the public space
|
||||
|
||||
|
||||
|
||||
|
||||
#### Themes using this layer
|
||||
|
||||
|
||||
|
||||
- [maps](https://mapcomplete.osm.be/maps)
|
||||
- [nature](https://mapcomplete.osm.be/nature)
|
||||
|
||||
|
||||
### all_streets
|
||||
|
||||
[Go to the source code](../assets/layers/all_streets/all_streets.json)
|
||||
|
||||
- Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`
|
||||
|
||||
|
||||
#### Themes using this layer
|
||||
|
||||
|
||||
|
||||
- [cyclestreets](https://mapcomplete.osm.be/cyclestreets)
|
||||
- [street_lighting](https://mapcomplete.osm.be/street_lighting)
|
||||
|
||||
|
||||
This document is autogenerated from AllKnownLayers.ts
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
Metatags
|
||||
==========
|
||||
|
||||
|
@ -11,6 +12,7 @@ The are calculated automatically on every feature when the data arrives in the w
|
|||
**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object
|
||||
|
||||
|
||||
|
||||
Metatags calculated by MapComplete
|
||||
------------------------------------
|
||||
|
||||
|
@ -19,6 +21,7 @@ The are calculated automatically on every feature when the data arrives in the w
|
|||
The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme
|
||||
|
||||
|
||||
|
||||
### _lat, _lon
|
||||
|
||||
|
||||
|
@ -28,6 +31,7 @@ The latitude and longitude of the point (or centerpoint in the case of a way/are
|
|||
|
||||
|
||||
|
||||
|
||||
### _layer
|
||||
|
||||
|
||||
|
@ -37,6 +41,7 @@ The layer-id to which this feature belongs. Note that this might be return any a
|
|||
|
||||
|
||||
|
||||
|
||||
### _surface, _surface:ha
|
||||
|
||||
|
||||
|
@ -46,6 +51,7 @@ The surface area of the feature, in square meters and in hectare. Not set on poi
|
|||
This is a lazy metatag and is only calculated when needed
|
||||
|
||||
|
||||
|
||||
### _length, _length:km
|
||||
|
||||
|
||||
|
@ -55,6 +61,7 @@ The total length of a feature in meters (and in kilometers, rounded to one decim
|
|||
|
||||
|
||||
|
||||
|
||||
### Theme-defined keys
|
||||
|
||||
|
||||
|
@ -64,6 +71,7 @@ If 'units' is defined in the layoutConfig, then this metatagger will rewrite the
|
|||
|
||||
|
||||
|
||||
|
||||
### _country
|
||||
|
||||
|
||||
|
@ -73,6 +81,7 @@ The country code of the property (with latlon2country)
|
|||
|
||||
|
||||
|
||||
|
||||
### _isOpen, _isOpen:description
|
||||
|
||||
|
||||
|
@ -82,6 +91,7 @@ If 'opening_hours' is present, it will add the current state of the feature (bei
|
|||
This is a lazy metatag and is only calculated when needed
|
||||
|
||||
|
||||
|
||||
### _direction:numerical, _direction:leftright
|
||||
|
||||
|
||||
|
@ -91,6 +101,7 @@ _direction:numerical is a normalized, numerical direction based on 'camera:direc
|
|||
|
||||
|
||||
|
||||
|
||||
### _now:date, _now:datetime, _loaded:date, _loaded:_datetime
|
||||
|
||||
|
||||
|
@ -100,6 +111,7 @@ Adds the time that the data got loaded - pretty much the time of downloading fro
|
|||
|
||||
|
||||
|
||||
|
||||
### _last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number, _backend
|
||||
|
||||
|
||||
|
@ -109,6 +121,7 @@ Information about the last edit of this object.
|
|||
|
||||
|
||||
|
||||
|
||||
### sidewalk:left, sidewalk:right, generic_key:left:property, generic_key:right:property
|
||||
|
||||
|
||||
|
@ -118,6 +131,7 @@ Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' an
|
|||
|
||||
|
||||
|
||||
|
||||
Calculating tags with Javascript
|
||||
----------------------------------
|
||||
|
||||
|
@ -173,6 +187,7 @@ Some advanced functions are available on **feat** as well:
|
|||
- [memberships](#memberships)
|
||||
- [get](#get)
|
||||
|
||||
|
||||
### distanceTo
|
||||
|
||||
Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object
|
||||
|
@ -180,6 +195,7 @@ Some advanced functions are available on **feat** as well:
|
|||
0. feature OR featureID OR longitude
|
||||
1. undefined OR latitude
|
||||
|
||||
|
||||
### overlapWith
|
||||
|
||||
Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.If the current feature is a point, all features that this point is embeded in are given.
|
||||
|
@ -191,12 +207,14 @@ For example to get all objects which overlap or embed from a layer, use `_contai
|
|||
|
||||
0. ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)
|
||||
|
||||
|
||||
### closest
|
||||
|
||||
Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet laoded)
|
||||
|
||||
0. list of features or a layer name or '*' to get all features
|
||||
|
||||
|
||||
### closestn
|
||||
|
||||
Given either a list of geojson features or a single layer name, gives the n closest objects which are nearest to the feature (excluding the feature itself). In the case of ways/polygons, only the centerpoint is considered. Returns a list of `{feat: geojson, distance:number}` the empty list if nothing is found (or not yet loaded)
|
||||
|
@ -208,6 +226,7 @@ If a 'unique tag key' is given, the tag with this key will only appear once (e.g
|
|||
2. unique tag key (optional)
|
||||
3. maxDistanceInMeters (optional)
|
||||
|
||||
|
||||
### memberships
|
||||
|
||||
Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of.
|
||||
|
@ -216,9 +235,12 @@ For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tag
|
|||
|
||||
|
||||
|
||||
|
||||
### get
|
||||
|
||||
Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ...
|
||||
|
||||
0. key
|
||||
Generated from SimpleMetaTagger, ExtraFunction
|
||||
|
||||
|
||||
This document is autogenerated from SimpleMetaTagger, ExtraFunction
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
Available types for text fields
|
||||
=================================
|
||||
|
||||
|
@ -12,7 +13,7 @@ A basic string
|
|||
|
||||
## text
|
||||
|
||||
A string, but allows input of longer strings more comfortably (a text area)
|
||||
A string, but allows input of longer strings more comfortably and supports newlines (a text area)
|
||||
|
||||
## date
|
||||
|
||||
|
@ -29,6 +30,7 @@ A geographical length in meters (rounded at two points). Will give an extra mini
|
|||
## wikidata
|
||||
|
||||
A wikidata identifier, e.g. Q42.
|
||||
|
||||
### Helper arguments
|
||||
|
||||
|
||||
|
@ -44,6 +46,7 @@ removePrefixes | remove these snippets of text from the start of the passed stri
|
|||
removePostfixes | remove these snippets of text from the end of the passed string to search
|
||||
|
||||
|
||||
|
||||
### Example usage
|
||||
|
||||
The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name
|
||||
|
@ -102,6 +105,7 @@ A phone number
|
|||
## opening_hours
|
||||
|
||||
Has extra elements to easily input when a POI is opened.
|
||||
|
||||
### Helper arguments
|
||||
|
||||
|
||||
|
@ -116,6 +120,7 @@ prefix | Piece of text that will always be added to the front of the generated o
|
|||
postfix | Piece of text that will always be added to the end of the generated opening hours
|
||||
|
||||
|
||||
|
||||
### Example usage
|
||||
|
||||
To add a conditional (based on time) access restriction:
|
||||
|
@ -138,4 +143,6 @@ postfix | Piece of text that will always be added to the end of the generated op
|
|||
|
||||
## color
|
||||
|
||||
Shows a color picker Generated from ValidatedTextField.ts
|
||||
Shows a color picker
|
||||
|
||||
This document is autogenerated from ValidatedTextField.ts
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
### Special tag renderings
|
||||
|
||||
|
||||
|
@ -27,14 +28,17 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam
|
|||
|
||||
|
||||
|
||||
|
||||
### all_tags
|
||||
|
||||
Prints all key-value pairs of the object - used for debugging
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{all_tags()}`
|
||||
|
||||
|
||||
|
||||
### image_carousel
|
||||
|
||||
Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)
|
||||
|
@ -43,11 +47,13 @@ name | default | description
|
|||
------ | --------- | -------------
|
||||
image key/prefix (multiple values allowed if comma-seperated) | image,mapillary,image,wikidata,wikimedia_commons,image,image | The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc...
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{image_carousel(image,mapillary,image,wikidata,wikimedia_commons,image,image)}`
|
||||
|
||||
|
||||
|
||||
### image_upload
|
||||
|
||||
Creates a button where a user can upload an image to IMGUR
|
||||
|
@ -57,11 +63,13 @@ name | default | description
|
|||
image-key | image | Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)
|
||||
label | Add image | The text to show on the button
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{image_upload(image,Add image)}`
|
||||
|
||||
|
||||
|
||||
### wikipedia
|
||||
|
||||
A box showing the corresponding wikipedia article - based on the wikidata tag
|
||||
|
@ -70,11 +78,13 @@ name | default | description
|
|||
------ | --------- | -------------
|
||||
keyToShowWikipediaFor | wikidata | Use the wikidata entry from this key to show the wikipedia article for
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height
|
||||
|
||||
|
||||
|
||||
### minimap
|
||||
|
||||
A small map showing the selected feature.
|
||||
|
@ -84,11 +94,13 @@ name | default | description
|
|||
zoomlevel | 18 | The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close
|
||||
idKey | id | (Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap.
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`
|
||||
|
||||
|
||||
|
||||
### sided_minimap
|
||||
|
||||
A small map showing _only one side_ the selected feature. *This features requires to have linerenderings with offset* as only linerenderings with a postive or negative offset will be shown. Note: in most cases, this map will be automatically introduced
|
||||
|
@ -97,11 +109,13 @@ name | default | description
|
|||
------ | --------- | -------------
|
||||
side | _undefined_ | The side to show, either `left` or `right`
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{sided_minimap(left)}`
|
||||
|
||||
|
||||
|
||||
### reviews
|
||||
|
||||
Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten
|
||||
|
@ -111,11 +125,13 @@ name | default | description
|
|||
subjectKey | name | The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>
|
||||
fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used
|
||||
|
||||
|
||||
|
||||
### opening_hours_table
|
||||
|
||||
Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.
|
||||
|
@ -126,11 +142,13 @@ key | opening_hours | The tagkey from which the table is constructed.
|
|||
prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__
|
||||
postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`
|
||||
|
||||
|
||||
|
||||
### live
|
||||
|
||||
Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}
|
||||
|
@ -141,11 +159,13 @@ Url | _undefined_ | The URL to load
|
|||
Shorthands | _undefined_ | A list of shorthands, of the format 'shorthandname:path.path.path'. separated by ;
|
||||
path | _undefined_ | The path (or shorthand) that should be returned
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}
|
||||
|
||||
|
||||
|
||||
### histogram
|
||||
|
||||
Create a histogram for a list of given values, read from the properties.
|
||||
|
@ -157,11 +177,13 @@ title | _empty string_ | The text to put above the given values column
|
|||
countHeader | _empty string_ | The text to put above the counts
|
||||
colors* | _undefined_ | (Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram
|
||||
|
||||
|
||||
|
||||
### share_link
|
||||
|
||||
Creates a link that (attempts to) open the native 'share'-screen
|
||||
|
@ -170,11 +192,13 @@ name | default | description
|
|||
------ | --------- | -------------
|
||||
url | _undefined_ | The url to share (default: current URL)
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
{share_link()} to share the current page, {share_link(<some_url>)} to share the given url
|
||||
|
||||
|
||||
|
||||
### canonical
|
||||
|
||||
Converts a short, canonical value into the long, translated text
|
||||
|
@ -183,11 +207,13 @@ name | default | description
|
|||
------ | --------- | -------------
|
||||
key | _undefined_ | The key of the tag to give the canonical text for
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
{canonical(length)} will give 42 metre (in french)
|
||||
|
||||
|
||||
|
||||
### import_button
|
||||
|
||||
This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes.
|
||||
|
@ -247,11 +273,13 @@ Snap onto layer(s)/replace geometry with this other way | _undefined_ | - If th
|
|||
- If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list
|
||||
snap max distance | 5 | The maximum distance that this point will move to snap onto a layer (in meters)
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18,,5)}`
|
||||
|
||||
|
||||
|
||||
### multi_apply
|
||||
|
||||
A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags
|
||||
|
@ -264,11 +292,13 @@ text | _undefined_ | The text to show on the button
|
|||
autoapply | _undefined_ | A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown
|
||||
overwrite | _undefined_ | If set to 'true', the tags on the other objects will always be overwritten. The default behaviour will be to only change the tags on other objects if they are either undefined or had the same value before the change
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}
|
||||
|
||||
|
||||
|
||||
### tag_apply
|
||||
|
||||
Shows a big button; clicking this button will apply certain tags onto the feature.
|
||||
|
@ -293,6 +323,9 @@ message | _undefined_ | The text to show to the contributor
|
|||
image | _undefined_ | An image to show to the contributor on the button
|
||||
id_of_object_to_apply_this_one | _undefined_ | If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element
|
||||
|
||||
|
||||
#### Example usage
|
||||
|
||||
`{tag_apply(survey_date:=$_now:date, Surveyed today!)}` Generated from UI/SpecialVisualisations.ts
|
||||
`{tag_apply(survey_date:=$_now:date, Surveyed today!)}`
|
||||
|
||||
This document is autogenerated from UI/SpecialVisualisations.ts
|
|
@ -121,6 +121,26 @@
|
|||
"description": "Layer 'Cafés and pubs' shows wheelchair=no with a fixed text, namely 'This place is not reachable with a wheelchair' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Cafés and pubs' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Cafés and pubs' shows service:electricity=limited with a fixed text, namely 'There are a few domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
"value": "limited"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Cafés and pubs' shows service:electricity=ask with a fixed text, namely 'There are no sockets available indoors to customers, but charging might be possible if the staff is asked' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
"value": "ask"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Cafés and pubs' shows service:electricity=no with a fixed text, namely 'There are a no domestic sockets available to customers seated indoors' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "dog",
|
||||
"description": "Layer 'Cafés and pubs' shows dog=yes with a fixed text, namely 'Dogs are allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')",
|
||||
|
|
|
@ -966,13 +966,13 @@
|
|||
},
|
||||
{
|
||||
"key": "location",
|
||||
"description": "Layer 'Bike parking' shows location=underground with a fixed text, namely 'Surface level parking' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')",
|
||||
"value": "underground"
|
||||
"description": "Layer 'Bike parking' shows location=surface with a fixed text, namely 'Surface level parking' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')",
|
||||
"value": "surface"
|
||||
},
|
||||
{
|
||||
"key": "location",
|
||||
"description": "Layer 'Bike parking' shows location=surface with a fixed text, namely 'Rooftop parking' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')",
|
||||
"value": "surface"
|
||||
"description": "Layer 'Bike parking' shows location=rooftop with a fixed text, namely 'Rooftop parking' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')",
|
||||
"value": "rooftop"
|
||||
},
|
||||
{
|
||||
"key": "location",
|
||||
|
|
|
@ -305,6 +305,26 @@
|
|||
"description": "Layer 'Restaurants and fast food' shows reusable_packaging:accept=only with a fixed text, namely 'You <b>must</b> bring your own container to order here.' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
"value": "only"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=limited with a fixed text, namely 'There are a few domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
"value": "limited"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=ask with a fixed text, namely 'There are no sockets available indoors to customers, but charging might be possible if the staff is asked' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
"value": "ask"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=no with a fixed text, namely 'There are a no domestic sockets available to customers seated indoors' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "dog",
|
||||
"description": "Layer 'Restaurants and fast food' shows dog=yes with a fixed text, namely 'Dogs are allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
|
||||
|
|
|
@ -310,6 +310,26 @@
|
|||
"description": "Layer 'Fries shop' shows reusable_packaging:accept=only with a fixed text, namely 'You <b>must</b> bring your own container to order here.' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "only"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Fries shop' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Fries shop' shows service:electricity=limited with a fixed text, namely 'There are a few domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "limited"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Fries shop' shows service:electricity=ask with a fixed text, namely 'There are no sockets available indoors to customers, but charging might be possible if the staff is asked' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "ask"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Fries shop' shows service:electricity=no with a fixed text, namely 'There are a no domestic sockets available to customers seated indoors' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "dog",
|
||||
"description": "Layer 'Fries shop' shows dog=yes with a fixed text, namely 'Dogs are allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
|
@ -625,6 +645,26 @@
|
|||
"description": "Layer 'Restaurants and fast food' shows reusable_packaging:accept=only with a fixed text, namely 'You <b>must</b> bring your own container to order here.' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "only"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=limited with a fixed text, namely 'There are a few domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "limited"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=ask with a fixed text, namely 'There are no sockets available indoors to customers, but charging might be possible if the staff is asked' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "ask"
|
||||
},
|
||||
{
|
||||
"key": "service:electricity",
|
||||
"description": "Layer 'Restaurants and fast food' shows service:electricity=no with a fixed text, namely 'There are a no domestic sockets available to customers seated indoors' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "dog",
|
||||
"description": "Layer 'Restaurants and fast food' shows dog=yes with a fixed text, namely 'Dogs are allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
|
||||
|
|
|
@ -89,6 +89,64 @@
|
|||
"description": "Layer 'Toilets' shows access=public with a fixed text, namely 'Public access' (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "public"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows and asks freeform values for key 'access' (in the MapComplete.osm.be theme 'Open Toilet Map')"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=yes with a fixed text, namely 'Public access' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=customers with a fixed text, namely 'Only access to customers' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "customers"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=no with a fixed text, namely 'Not accessible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=key with a fixed text, namely 'Accessible, but one has to ask a key to enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "key"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=public with a fixed text, namely 'Public access' (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "public"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows and asks freeform values for key 'access' (in the MapComplete.osm.be theme 'Open Toilet Map')"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=yes with a fixed text, namely 'Public access' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=customers with a fixed text, namely 'Only access to customers' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "customers"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=no with a fixed text, namely 'Not accessible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=key with a fixed text, namely 'Accessible, but one has to ask a key to enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "key"
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"description": "Layer 'Toilets' shows access=public with a fixed text, namely 'Public access' (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
"value": "public"
|
||||
},
|
||||
{
|
||||
"key": "fee",
|
||||
"description": "Layer 'Toilets' shows fee=yes with a fixed text, namely 'These are paid toilets' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
|
||||
|
|
|
@ -20,139 +20,164 @@ the URL-parameters are stated in the part between the `?` and the `#`. There are
|
|||
Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.
|
||||
|
||||
|
||||
|
||||
fs-userbadge
|
||||
--------------
|
||||
|
||||
Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-search
|
||||
-----------
|
||||
|
||||
Disables/Enables the search bar The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-background
|
||||
---------------
|
||||
|
||||
Disables/Enables the background layer control The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-filter
|
||||
-----------
|
||||
|
||||
Disables/Enables the filter The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-add-new
|
||||
------------
|
||||
|
||||
Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-welcome-message
|
||||
--------------------
|
||||
|
||||
Disables/enables the help menu or welcome message The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-iframe-popout
|
||||
------------------
|
||||
|
||||
Disables/Enables the iframe-popout button. If in iframe mode and the welcome message is hidden, a popout button to the full mapcomplete instance is shown instead (unless disabled with this switch) The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-more-quests
|
||||
----------------
|
||||
|
||||
Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-share-screen
|
||||
-----------------
|
||||
|
||||
Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-geolocation
|
||||
----------------
|
||||
|
||||
Disables/Enables the geolocation button The default value is _true_
|
||||
|
||||
|
||||
|
||||
fs-all-questions
|
||||
------------------
|
||||
|
||||
Always show all questions The default value is _false_
|
||||
|
||||
|
||||
|
||||
fs-export
|
||||
-----------
|
||||
|
||||
Enable the export as GeoJSON and CSV button The default value is _false_
|
||||
|
||||
|
||||
|
||||
fs-pdf
|
||||
--------
|
||||
|
||||
Enable the PDF download button The default value is _false_
|
||||
|
||||
|
||||
|
||||
backend
|
||||
---------
|
||||
|
||||
The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_
|
||||
|
||||
|
||||
|
||||
test
|
||||
------
|
||||
|
||||
If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_
|
||||
|
||||
|
||||
|
||||
debug
|
||||
-------
|
||||
|
||||
If true, shows some extra debugging help such as all the available tags on every object The default value is _false_
|
||||
|
||||
|
||||
|
||||
fake-user
|
||||
-----------
|
||||
|
||||
If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_
|
||||
|
||||
|
||||
|
||||
overpassUrl
|
||||
-------------
|
||||
|
||||
Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter The default value is _https://overpass-api.de/api/interpreter,https://overpass.kumi.systems/api/interpreter,https://overpass.openstreetmap.ru/cgi/interpreter_
|
||||
|
||||
|
||||
|
||||
overpassTimeout
|
||||
-----------------
|
||||
|
||||
Set a different timeout (in seconds) for queries in overpass The default value is _30_
|
||||
|
||||
|
||||
|
||||
overpassMaxZoom
|
||||
-----------------
|
||||
|
||||
point to switch between OSM-api and overpass The default value is _17_
|
||||
|
||||
|
||||
|
||||
osmApiTileSize
|
||||
----------------
|
||||
|
||||
Tilesize when the OSM-API is used to fetch data within a BBOX The default value is _18_
|
||||
|
||||
|
||||
|
||||
background
|
||||
------------
|
||||
|
||||
The id of the background layer to start with The default value is _osm_
|
||||
|
||||
|
||||
|
||||
layer-<layer-id>
|
||||
------------------
|
||||
|
||||
Wether or not the layer with id <layer-id> is shown The default value is _true_ Generated from QueryParameters
|
||||
Wether or not the layer with id <layer-id> is shown The default value is _true_
|
||||
|
||||
This document is autogenerated from QueryParameters
|
|
@ -5,11 +5,10 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {QueryParameters} from "../Web/QueryParameters";
|
||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource";
|
||||
|
||||
export default class GeoLocationHandler extends VariableUiElement {
|
||||
|
||||
public readonly currentLocation: FeatureSource
|
||||
private readonly currentLocation: FeatureSource
|
||||
|
||||
/**
|
||||
* Wether or not the geolocation is active, aka the user requested the current location
|
||||
|
@ -59,13 +58,13 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
|
||||
constructor(
|
||||
state: {
|
||||
currentGPSLocation: UIEventSource<Coordinates>,
|
||||
currentUserLocation: FeatureSource,
|
||||
leafletMap: UIEventSource<any>,
|
||||
layoutToUse: LayoutConfig,
|
||||
featureSwitchGeolocation: UIEventSource<boolean>
|
||||
}
|
||||
) {
|
||||
const currentGPSLocation = state.currentGPSLocation
|
||||
const currentGPSLocation = new UIEventSource<Coordinates>(undefined, "GPS-coordinate")
|
||||
const leafletMap = state.leafletMap
|
||||
const hasLocation = currentGPSLocation.map(
|
||||
(location) => location !== undefined
|
||||
|
@ -182,16 +181,16 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
}
|
||||
})
|
||||
|
||||
this.currentLocation = new StaticFeatureSource([], false)
|
||||
this.currentLocation = state.currentUserLocation
|
||||
this._currentGPSLocation.addCallback((location) => {
|
||||
self._previousLocationGrant.setData("granted");
|
||||
|
||||
const feature = {
|
||||
"type": "Feature",
|
||||
properties: {
|
||||
id: "gps",
|
||||
"user:location": "yes",
|
||||
"accuracy": location.accuracy,
|
||||
"speed": location.speed,
|
||||
...location
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
|
|
|
@ -75,6 +75,8 @@ export default class FeaturePipeline {
|
|||
constructor(
|
||||
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
||||
state: {
|
||||
readonly homeLocation: FeatureSourceForLayer & Tiled;
|
||||
readonly currentUserLocation: FeatureSourceForLayer & Tiled;
|
||||
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||
readonly locationControl: UIEventSource<Loc>,
|
||||
readonly selectedElement: UIEventSource<any>,
|
||||
|
@ -152,6 +154,16 @@ export default class FeaturePipeline {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (id === "gps_location") {
|
||||
hierarchy.registerTile(state.currentUserLocation)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "home_location") {
|
||||
hierarchy.registerTile(state.homeLocation)
|
||||
continue
|
||||
}
|
||||
|
||||
if (source.geojsonSource === undefined) {
|
||||
// This is an OSM layer
|
||||
// We load the cached values and register them
|
||||
|
|
|
@ -4,17 +4,18 @@ import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
|||
import {BBox} from "../../BBox";
|
||||
|
||||
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled {
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
public readonly name: string = "SimpleFeatureSource";
|
||||
public readonly layer: FilteredLayer;
|
||||
public readonly bbox: BBox = BBox.global;
|
||||
public readonly tileIndex: number;
|
||||
|
||||
constructor(layer: FilteredLayer, tileIndex: number) {
|
||||
constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature:any; freshness: Date }[]>) {
|
||||
this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")"
|
||||
this.layer = layer
|
||||
this.tileIndex = tileIndex ?? 0;
|
||||
this.bbox = BBox.fromTileIndex(this.tileIndex)
|
||||
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
}
|
||||
|
||||
}
|
|
@ -286,7 +286,7 @@ export class ChangesetHandler {
|
|||
["language", Locale.language.data],
|
||||
["host", window.location.host],
|
||||
["path", path],
|
||||
["source", State.state.currentGPSLocation.data !== undefined ? "survey" : undefined],
|
||||
["source", State.state.currentUserLocation.features.data.length > 0 ? "survey" : undefined],
|
||||
["imagery", State.state.backgroundLayer.data.id],
|
||||
...changesetTags.map(cstag => [cstag.key, cstag.value])
|
||||
]
|
||||
|
|
|
@ -14,6 +14,8 @@ import {QueryParameters} from "../Web/QueryParameters";
|
|||
import * as personal from "../../assets/themes/personal/personal.json";
|
||||
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
|
||||
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer";
|
||||
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource";
|
||||
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource";
|
||||
|
||||
/**
|
||||
* Contains all the leaflet-map related state
|
||||
|
@ -44,7 +46,17 @@ export default class MapState extends UserRelatedState {
|
|||
/**
|
||||
* The location as delivered by the GPS
|
||||
*/
|
||||
public currentGPSLocation: UIEventSource<Coordinates> = new UIEventSource<Coordinates>(undefined);
|
||||
public currentUserLocation: FeatureSourceForLayer & Tiled;
|
||||
|
||||
/**
|
||||
* All previously visited points
|
||||
*/
|
||||
public historicalUserLocations: FeatureSourceForLayer & Tiled;
|
||||
|
||||
/**
|
||||
* A feature source containing the current home location of the user
|
||||
*/
|
||||
public homeLocation: FeatureSourceForLayer & Tiled
|
||||
|
||||
public readonly mainMapObject: BaseUIElement & MinimapObj;
|
||||
|
||||
|
@ -120,6 +132,9 @@ export default class MapState extends UserRelatedState {
|
|||
|
||||
this.lockBounds()
|
||||
this.AddAllOverlaysToMap(this.leafletMap)
|
||||
|
||||
this.initHomeLocation()
|
||||
this.initGpsLocation()
|
||||
}
|
||||
|
||||
public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) {
|
||||
|
@ -164,6 +179,50 @@ export default class MapState extends UserRelatedState {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
private initGpsLocation(){
|
||||
// Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler
|
||||
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location")[0]
|
||||
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0));
|
||||
}
|
||||
|
||||
private initHomeLocation() {
|
||||
const empty = []
|
||||
const feature = UIEventSource.ListStabilized(this.osmConnection.userDetails.map(userDetails => {
|
||||
|
||||
if (userDetails === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const home = userDetails.home;
|
||||
if (home === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return [home.lon, home.lat]
|
||||
})).map(homeLonLat => {
|
||||
if (homeLonLat === undefined) {
|
||||
return empty
|
||||
}
|
||||
return [{
|
||||
feature: {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"id":"home",
|
||||
"user:home": "yes",
|
||||
"_lon": homeLonLat[0],
|
||||
"_lat": homeLonLat[1]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": homeLonLat
|
||||
}
|
||||
}, freshness: new Date()
|
||||
}]
|
||||
})
|
||||
|
||||
const flayer = this.filteredLayers.data.filter(l => l.layerDef.id === "home_location")[0]
|
||||
this.homeLocation = new SimpleFeatureSource(flayer, Tiles.tile_index(0, 0, 0), feature)
|
||||
|
||||
}
|
||||
|
||||
private InitializeFilteredLayers() {
|
||||
// Initialize the filtered layers state
|
||||
|
|
|
@ -36,10 +36,7 @@ export default class UserRelatedState extends ElementsState {
|
|||
* WHich other themes the user previously visited
|
||||
*/
|
||||
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
|
||||
/**
|
||||
* A feature source containing the current home location of the user
|
||||
*/
|
||||
public homeLocation: FeatureSource
|
||||
|
||||
|
||||
constructor(layoutToUse: LayoutConfig) {
|
||||
super(layoutToUse);
|
||||
|
@ -88,7 +85,6 @@ export default class UserRelatedState extends ElementsState {
|
|||
|
||||
|
||||
this.InitializeLanguage();
|
||||
this.initHomeLocation()
|
||||
new SelectedElementTagsUpdater(this)
|
||||
|
||||
}
|
||||
|
@ -116,37 +112,4 @@ export default class UserRelatedState extends ElementsState {
|
|||
.ping();
|
||||
}
|
||||
|
||||
private initHomeLocation() {
|
||||
const empty = []
|
||||
const feature = UIEventSource.ListStabilized(this.osmConnection.userDetails.map(userDetails => {
|
||||
|
||||
if (userDetails === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const home = userDetails.home;
|
||||
if (home === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return [home.lon, home.lat]
|
||||
})).map(homeLonLat => {
|
||||
if (homeLonLat === undefined) {
|
||||
return empty
|
||||
}
|
||||
return [{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"user:home": "yes",
|
||||
"_lon": homeLonLat[0],
|
||||
"_lat": homeLonLat[1]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": homeLonLat
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
this.homeLocation = new StaticFeatureSource(feature, false)
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,10 @@ import LineRenderingConfigJson from "./Json/LineRenderingConfigJson";
|
|||
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
import Combine from "../../UI/Base/Combine";
|
||||
import Title from "../../UI/Base/Title";
|
||||
import List from "../../UI/Base/List";
|
||||
import Link from "../../UI/Base/Link";
|
||||
|
||||
export default class LayerConfig extends WithContextLoader {
|
||||
|
||||
|
@ -369,6 +373,41 @@ export default class LayerConfig extends WithContextLoader {
|
|||
|
||||
}
|
||||
|
||||
public GenerateDocumentation(usedInThemes: string[], addedByDefault = false, canBeIncluded = true): BaseUIElement {
|
||||
const extraProps = []
|
||||
|
||||
if (canBeIncluded) {
|
||||
if (this.title === undefined) {
|
||||
extraProps.push("Not clickable by default. If you import this layer in your theme, override `title` to make this clickable")
|
||||
}
|
||||
if (this.name === undefined) {
|
||||
extraProps.push("Not visible in the layer selection by default. If you want to make this layer toggable, override `name`")
|
||||
}
|
||||
if (this.mapRendering.length === 0) {
|
||||
extraProps.push("Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`")
|
||||
}
|
||||
} else {
|
||||
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.")
|
||||
}
|
||||
|
||||
|
||||
let usingLayer: BaseUIElement[] = []
|
||||
if (usedInThemes?.length > 0 && !addedByDefault) {
|
||||
usingLayer = [new Title("Themes using this layer", 4),
|
||||
new List((usedInThemes ?? []).map(id => new Link(id, "https://mapcomplete.osm.be/" + id)))
|
||||
]
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
new Title(this.id, 3),
|
||||
addedByDefault ? "**This layer is included automatically in every theme. This layer might contain no points**" : undefined,
|
||||
new Link("Go to the source code", `../assets/layers/${this.id}/${this.id}.json`),
|
||||
this.description,
|
||||
new List(extraProps),
|
||||
...usingLayer
|
||||
])
|
||||
}
|
||||
|
||||
public CustomCodeSnippets(): string[] {
|
||||
if (this.calculatedTags === undefined) {
|
||||
return [];
|
||||
|
|
|
@ -160,12 +160,16 @@ export default class LayoutConfig {
|
|||
if (typeof layer === "string") {
|
||||
if (AllKnownLayers.sharedLayersJson.get(layer) !== undefined) {
|
||||
if (json.overrideAll !== undefined) {
|
||||
let lyr = JSON.parse(JSON.stringify(AllKnownLayers.sharedLayersJson[layer]));
|
||||
let lyr = JSON.parse(JSON.stringify(AllKnownLayers.sharedLayersJson.get(layer)));
|
||||
const newLayer = new LayerConfig(Utils.Merge(json.overrideAll, lyr), `${json.id}+overrideAll.layers[${i}]`, official)
|
||||
result.push(newLayer)
|
||||
return
|
||||
} else {
|
||||
result.push(AllKnownLayers.sharedLayers[layer])
|
||||
const shared = AllKnownLayers.sharedLayers.get(layer)
|
||||
if(shared === undefined){
|
||||
throw `Shared layer ${layer} not found (at ${context}.layers[${i}])`
|
||||
}
|
||||
result.push(shared)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -179,8 +183,7 @@ export default class LayoutConfig {
|
|||
layer = Utils.Merge(json.overrideAll, layer);
|
||||
}
|
||||
// @ts-ignore
|
||||
const newLayer = new LayerConfig(layer, `${json.id}.layers[${i}]`, official)
|
||||
result.push(newLayer)
|
||||
result.push(new LayerConfig(layer, `${json.id}.layers[${i}]`, official))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -204,13 +207,19 @@ export default class LayoutConfig {
|
|||
if (json.overrideAll !== undefined) {
|
||||
newLayer = Utils.Merge(json.overrideAll, newLayer);
|
||||
}
|
||||
// @ts-ignore
|
||||
const layerConfig = new LayerConfig(newLayer, `${json.id}.layers[${i}]`, official)
|
||||
result.push(layerConfig)
|
||||
result.push(new LayerConfig(newLayer, `${json.id}.layers[${i}]`, official))
|
||||
return
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
// Some special layers which are always included by default
|
||||
for (const defaultLayer of AllKnownLayers.added_by_default) {
|
||||
if(result.some(l => l?.id === defaultLayer)){
|
||||
continue; // Already added
|
||||
}
|
||||
result.push(AllKnownLayers.sharedLayers.get(defaultLayer))
|
||||
}
|
||||
|
||||
return {layers: result, extractAllNodes: exportAllNodes}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ export default class Title extends BaseUIElement {
|
|||
const embedded = " " + this._embedded.AsMarkdown() + " ";
|
||||
|
||||
if (this._level == 1) {
|
||||
return "\n" + embedded + "\n" + "=".repeat(embedded.length) + "\n\n"
|
||||
return "\n\n" + embedded + "\n" + "=".repeat(embedded.length) + "\n\n"
|
||||
}
|
||||
|
||||
if (this._level == 2) {
|
||||
return "\n" + embedded + "\n" + "-".repeat(embedded.length) + "\n\n"
|
||||
return "\n\n" + embedded + "\n" + "-".repeat(embedded.length) + "\n\n"
|
||||
}
|
||||
|
||||
return "\n" + "#".repeat(this._level) + embedded + "\n\n";
|
||||
return "\n\n" + "#".repeat(this._level) + embedded + "\n\n";
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
|
|
|
@ -161,7 +161,7 @@ export default abstract class BaseUIElement {
|
|||
}
|
||||
|
||||
public AsMarkdown(): string {
|
||||
throw "AsMarkdown is not implemented by " + this.constructor.name
|
||||
throw "AsMarkdown is not implemented by " + this.constructor.name+"; implement it in the subclass"
|
||||
}
|
||||
|
||||
protected abstract InnerConstructElement(): HTMLElement;
|
||||
|
|
|
@ -15,13 +15,6 @@ export default class RightControls extends Combine {
|
|||
state
|
||||
)
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("gps_location"),
|
||||
leafletMap: state.leafletMap,
|
||||
enablePopups: true,
|
||||
features: geolocatioHandler.currentLocation
|
||||
})
|
||||
|
||||
const geolocationButton = new Toggle(
|
||||
new MapControlButton(
|
||||
geolocatioHandler
|
||||
|
|
|
@ -63,7 +63,6 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
this.SetClass("w-full")
|
||||
}
|
||||
|
||||
|
||||
public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
|
||||
fixed?: string,
|
||||
special?: {
|
||||
|
|
|
@ -227,4 +227,8 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
return allIcons.filter(icon => icon != undefined)
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
return this.txt
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "conflation",
|
||||
"description": "This is a special meta_layer which render geometry-changes for inspection",
|
||||
"description": "If the import-button is set to conflate two ways, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme.",
|
||||
"minzoom": 1,
|
||||
"source": {
|
||||
"osmTags": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "gps_location",
|
||||
"description": "Meta layer showing the current location of the user",
|
||||
"description": "Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) returned by the browser.",
|
||||
"minzoom": 0,
|
||||
"source": {
|
||||
"osmTags": "user:location=yes"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "home_location",
|
||||
"description": "Meta layer showing the home location of the user",
|
||||
"description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.",
|
||||
"minzoom": 0,
|
||||
"source": {
|
||||
"osmTags": "user:home=yes"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "left_right_style",
|
||||
"description": "Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads",
|
||||
"description": "Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads. Cannot be included in a theme",
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"or": [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "type_node",
|
||||
"description": "This is a special meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list",
|
||||
"description": "This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.",
|
||||
"minzoom": 18,
|
||||
"source": {
|
||||
"osmTags": "id~node/.*"
|
||||
|
|
|
@ -11,13 +11,15 @@ import {QueryParameters} from "../Logic/Web/QueryParameters";
|
|||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
||||
import Minimap from "../UI/Base/Minimap";
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||
import AllKnownLayers from "../Customizations/AllKnownLayers";
|
||||
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
||||
|
||||
Utils.runningFromConsole = true;
|
||||
|
||||
|
||||
function WriteFile(filename, html: string | BaseUIElement, autogenSource: string[]): void {
|
||||
writeFileSync(filename, new Combine([Translations.W(html),
|
||||
"Generated from " + autogenSource.join(", ")
|
||||
"\n\nThis document is autogenerated from " + autogenSource.join(", ")
|
||||
]).AsMarkdown());
|
||||
}
|
||||
|
||||
|
@ -25,7 +27,7 @@ WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), ["
|
|||
WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()]).SetClass("flex-col"),
|
||||
["SimpleMetaTagger", "ExtraFunction"])
|
||||
WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), ["ValidatedTextField.ts"]);
|
||||
|
||||
WriteFile("./Docs/BuiltinLayers.md", AllKnownLayouts.GenLayerOverviewText(), ["AllKnownLayers.ts"])
|
||||
Minimap.createMiniMap = _ => {
|
||||
console.log("Not creating a minimap, it is disabled");
|
||||
return undefined
|
||||
|
|
|
@ -196,7 +196,7 @@ class LayerOverviewUtils {
|
|||
}
|
||||
themeConfigs.push(theme)
|
||||
} catch (e) {
|
||||
themeErrorCount.push("Could not parse theme " + themeFile["id"] + "due to", e)
|
||||
themeErrorCount.push("Could not parse theme " + themeFile["id"] + " due to", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue