Merge branch 'develop' into post-partner

This commit is contained in:
Robin van der Linde 2021-11-13 20:33:40 +01:00
commit eab7a9c477
458 changed files with 91460 additions and 58297 deletions

View file

@ -13,12 +13,7 @@ name: "CodeQL"
on:
push:
branches: [ develop, master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '21 18 * * 3'
branches: [ master ]
jobs:
analyze:
@ -37,34 +32,34 @@ jobs:
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -1,14 +1,33 @@
import * as known_layers from "../assets/generated/known_layers_and_themes.json"
import {Utils} from "../Utils";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
import SharedTagRenderings from "./SharedTagRenderings";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import WithContextLoader from "../Models/ThemeConfig/WithContextLoader";
export default class AllKnownLayers {
public static inited = (_ => {
WithContextLoader.getKnownTagRenderings = (id => AllKnownLayers.getTagRendering(id))
return true
})()
// Must be below the list...
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
public static added_by_default: string[] = ["gps_location","gps_location_history", "home_location", "gps_track",]
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 +35,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)
@ -48,7 +66,7 @@ export default class AllKnownLayers {
return sharedLayers;
}
private static getSharedLayersJson(): Map<string, any> {
private static getSharedLayersJson(): Map<string, LayerConfigJson> {
const sharedLayers = new Map<string, any>();
for (const layer of known_layers.layers) {
sharedLayers.set(layer.id, layer);
@ -57,5 +75,41 @@ export default class AllKnownLayers {
return sharedLayers;
}
/**
* Gets the appropriate tagRenderingJSON
* Allows to steal them from other layers.
* This will add the tags of the layer to the configuration though!
* @param renderingId
*/
static getTagRendering(renderingId: string): TagRenderingConfigJson {
if(renderingId.indexOf(".") < 0){
return SharedTagRenderings.SharedTagRenderingJson.get(renderingId)
}
const [layerId, id] = renderingId.split(".")
const layer = AllKnownLayers.getSharedLayersJson().get(layerId)
if(layer === undefined){
if(Utils.runningFromConsole){
// Probably generating the layer overview
return <TagRenderingConfigJson> {
id: "dummy"
}
}
throw "Builtin layer "+layerId+" not found"
}
const renderings = layer?.tagRenderings ?? []
for (const rendering of renderings) {
if(rendering["id"] === id){
const found = <TagRenderingConfigJson> JSON.parse(JSON.stringify(rendering))
if(found.condition === undefined){
found.condition = layer.source.osmTags
}else{
found.condition = {and: [found.condition, layer.source.osmTags]}
}
return found
}
}
throw `The rendering with id ${id} was not found in the builtin layer ${layerId}. Try one of ${Utils.NoNull(renderings.map(r => r["id"])).join(", ")}`
}
}

View file

@ -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 {
@ -9,16 +13,16 @@ export class AllKnownLayouts {
public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts();
public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts);
public static AllPublicLayers(){
const allLayers : LayerConfig[] = []
public static AllPublicLayers() {
const allLayers: LayerConfig[] = []
const seendIds = new Set<string>()
const publicLayouts = AllKnownLayouts.layoutsList.filter(l => !l.hideFromOverview)
for (const layout of publicLayouts) {
if(layout.hideFromOverview){
if (layout.hideFromOverview) {
continue
}
for (const layer of layout.layers) {
if(seendIds.has(layer.id)){
if (seendIds.has(layer.id)) {
continue
}
seendIds.add(layer.id)
@ -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 = []

View file

@ -15,7 +15,7 @@ export default class SharedTagRenderings {
const d = new Map<string, TagRenderingConfig>()
for (const key of Array.from(configJsons.keys())) {
try {
d.set(key, new TagRenderingConfig(configJsons.get(key), `SharedTagRenderings.${key}`))
d.set(key, new TagRenderingConfig(configJsons.get(key), `SharedTagRenderings.${key}`))
} catch (e) {
if (!Utils.runningFromConsole) {
console.error("BUG: could not parse", key, " from questions.json or icons.json - this error happened during the build step of the SharedTagRenderings", e)
@ -38,7 +38,10 @@ export default class SharedTagRenderings {
dict.set(key, <TagRenderingConfigJson>icons[key])
}
dict.forEach((value, key) => value.id = key)
return dict;
}
}

236
Docs/BuiltinLayers.md Normal file
View file

@ -0,0 +1,236 @@
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)
- [gps_track](#gps_track)
- [type_node](#type_node)
- [conflation](#conflation)
- [left_right_style](#left_right_style)
### gps_location
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.
[Go to the source code](../assets/layers/gps_location/gps_location.json)
- **This layer is included automatically in every theme. This layer might contain no points**
- 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`
### home_location
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.
[Go to the source code](../assets/layers/home_location/home_location.json)
- **This layer is included automatically in every theme. This layer might contain no points**
- 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`
### gps_track
Meta layer showing the previou locations of the user. Add this to your theme and override the icon to change the appearance of the current location.
[Go to the source code](../assets/layers/gps_track/gps_track.json)
- **This layer is included automatically in every theme. This layer might contain no points**
- Not visible in the layer selection by default. If you want to make this layer toggable, override `name`
### type_node
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.
[Go to the source code](../assets/layers/type_node/type_node.json)
- Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`
### conflation
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.
[Go to the source code](../assets/layers/conflation/conflation.json)
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
### left_right_style
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
[Go to the source code](../assets/layers/left_right_style/left_right_style.json)
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
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
A facility where bicycles can be lent for longer period of times
[Go to the source code](../assets/layers/bicycle_library/bicycle_library.json)
#### 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
A map, meant for tourists which is permanently installed in the public space
[Go to the source code](../assets/layers/map/map.json)
#### 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

View file

@ -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

View file

@ -1,32 +1,42 @@
Rights of contributors
======================
Rights of contributors
======================
If a contributor is quite active within MapComplete, this contributor might be granted access to the main repository.
If you have access to the repository, you can make a fork of an already existing branch and push this new branch to github.
This means that this branch will be _automatically built_ and be **deployed** to `https://pietervdvn.github.io/mc/<branchname>`. You can see the deploy process on [Github Actions](https://github.com/pietervdvn/MapComplete/actions).
Don't worry about pushing too much. These deploys are free and totally automatic. They might fail if something is wrong, but this will hinder no-one.
If you have access to the repository, you can make a fork of an already existing branch and push this new branch to
github. This means that this branch will be _automatically built_ and be **deployed**
to `https://pietervdvn.github.io/mc/<branchname>`. You can see the deploy process
on [Github Actions](https://github.com/pietervdvn/MapComplete/actions). Don't worry about pushing too much. These
deploys are free and totally automatic. They might fail if something is wrong, but this will hinder no-one.
Additionaly, some other maintainer might step in and merge the latest develop with your branch, making later pull requests easier.
Additionaly, some other maintainer might step in and merge the latest develop with your branch, making later pull
requests easier.
Don't worry about bugs
----------------------
As a non-admin contributor, you can _not_ make changes to the `master` nor to the `develop` branch. This is because, as soon as master is changed, this is built and deployed on `mapcomplete.osm.be`, which a lot of people use. An error there will cause a lot of grieve.
As a non-admin contributor, you can _not_ make changes to the `master` nor to the `develop` branch. This is because, as
soon as master is changed, this is built and deployed on `mapcomplete.osm.be`, which a lot of people use. An error there
will cause a lot of grieve.
A push on `develop` is automatically deployed to [pietervdvn.github.io/mc/develop] and is used by quite some people to. People using this version should know that this is a testing ground for new features and might contain a bug every now and then.
A push on `develop` is automatically deployed to [pietervdvn.github.io/mc/develop] and is used by quite some people to.
People using this version should know that this is a testing ground for new features and might contain a bug every now
and then.
In other words, to get your theme deployed on the main instances, you'll still have to create a pull request. The maintainers will then doublecheck and pull it in.
In other words, to get your theme deployed on the main instances, you'll still have to create a pull request. The
maintainers will then doublecheck and pull it in.
If you have a local repository
------------------------------
If you have made a fork earlier and have received contributor rights, you need to tell your local git repository that pushing to the main repository is possible.
If you have made a fork earlier and have received contributor rights, you need to tell your local git repository that
pushing to the main repository is possible.
To do this:
1. type `git remote add upstream git@github.com:pietervdvn/MapComplete`
2. Run `git push upstream` to push your latest changes to the main repo (and not your fork). Running `git push` will push to your fork.
2. Run `git push upstream` to push your latest changes to the main repo (and not your fork). Running `git push` will
push to your fork.
Alternatively, if you don't have any unmerged changes, you can remove your local copy and clone `pietervdvn/MapComplete` again to start fresh.
Alternatively, if you don't have any unmerged changes, you can remove your local copy and clone `pietervdvn/MapComplete`
again to start fresh.

View file

@ -28,14 +28,15 @@ To develop and build MapComplete, you
0. Make a fork and clone the repository.
0. Install the nodejs version specified in [.tool-versions](./.tool-versions)
- On linux: install npm first `sudo apt install npm`, then install `n` using npm: ` npm install -g n`, which can then install node with `n install <node-version>`
- You can [use asdf to manage your runtime versions](https://asdf-vm.com/).
- On linux: install npm first `sudo apt install npm`, then install `n` using npm: ` npm install -g n`, which can
then install node with `n install <node-version>`
- You can [use asdf to manage your runtime versions](https://asdf-vm.com/).
0. Install `npm`. Linux: `sudo apt install npm` (or your favourite package manager), Windows: install
nodeJS: https://nodejs.org/en/download/
0. On iOS, install `wget` (`brew install wget`)
0. Run `npm run init` which …
- runs `npm install`
- generates some additional dependencies and files
- runs `npm install`
- generates some additional dependencies and files
0. Run `npm run start` to host a local testversion at http://localhost:1234/index.html
0. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename`
or `userlayout=true#<layout configuration>` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (
@ -106,7 +107,8 @@ Try removing `node_modules`, `package-lock.json` and `.cache`
Misc setup
----------
The json-git-merger is used to quickly merge translation files, [documentation here](https://github.com/jonatanpedersen/git-json-merge#single-project--directory)
~~The json-git-merger is used to quickly merge translation files, [documentation here](https://github.com/jonatanpedersen/git-json-merge#single-project--directory).~~
This merge driver is broken and would sometimes drop new questions or duplicate them... Not a good idea!
Overview of package.json-scripts
--------------------------------

View file

@ -105,11 +105,68 @@ Every field is documented in the source code itself - you can find them here:
- [The `TagRendering`](https://github.com/pietervdvn/MapComplete/blob/master/Models/ThemeConfig/Json/TagRenderingConfigJson.ts)
- At last, the exact semantics of tags is documented [here](Tags_format.md)
A JSON-schema file is available in Docs/Schemas - use LayoutConfig.schema.json to validate a theme file.
### MetaTags
There are few tags available that are calculated for convenience - e.g. the country an object is located
at. [An overview of all these metatags is available here](Docs/CalculatedTags.md)
### TagRendering groups
A tagRendering can have a `group`-attribute, which acts as a tag.
All tagRenderings with the same group name will be rendered together, in the same order as they were defined.
For example, if the defined tagrenderings have groups `A A B A A B B B`, the group order is `A B` and first all tagrenderings from group A will be rendered (thus numbers 0, 1, 3 and 4) followed by the question box for this group.
Then, all the tagRenderings for group B will be shown, thus number 2, 5, 6 and 7, again followed by their questionbox.
Additionally, every tagrendering will receive a the groupname as class in the HTML, which can be used to hook up custom CSS.
If no group tag is given, the group is `` (empty string)
### Deciding the questions position
By default, the questions are shown just beneath their group.
To override this behaviour, one can add a tagrendering with id `questions` to move the questions up.
To add a title to the questions, one can add a `render` and a condition.
To change the behaviour of the questionbox to show _all_ questions at once, one can use a helperArgs in the freeform field with option `showAllQuestions`.
For example, to show the questions on top, use:
```
"tagRenderings": [
{ "id": "questions" }
{ ... some tagrendering ... }
{ ... more tagrendering ...}
]
```
To show _all_ the questions of a group at once in the middle of the tagrenderings, with a header, use:
```
"tagRenderings": [
{
"id": "questions" ,
"group": "groupname",
"render": {
"en": "<h3>Technical questions</h3>The following questions are very technical!<br />{questions}
},
"freeform": {
"key": "questions",
"helperArgs": {
"showAllQuestions": true
}
}
}
{ ... some tagrendering ... }
{ ... more tagrendering ...}
]
```
Some hints
------------
@ -173,16 +230,7 @@ Instead, make one layer for one kind of object and change the icon based on attr
Using layers as filters - this doesn't work!
_All_ data is downloaded in one go and cached locally first. The layer selection (bottom left of the live app) then
selects _anything_ that matches the criteria. This match is then passed of to the rendering layer, which selects the
layer independently. This means that a feature can show up, even if it's layer is unselected!
For example, in the [cyclofix-theme](https://mapcomplete.osm.org/cyclofix), there is the layer with _bike-wash_ for do
it yourself bikecleaning - points marked with `service:bicycle:cleaning`. However, a bicycle repair shop can offer this
service too!
If all the layers are deselected except the bike wash layer, a shop having this tag will still match and will still show
up as shop.
Use the `filter`-functionality instead
### Not reading the .JSON-specs

View file

@ -1,145 +1,181 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: G Pages: 1 -->
<svg width="664pt" height="566pt"
viewBox="0.00 0.00 664.25 566.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 562)">
<title>G</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-562 660.25,-562 660.25,4 -4,4"/>
<!-- init -->
<g id="node1" class="node">
<title>init</title>
<polygon fill="none" stroke="black" points="242.25,-558 188.25,-558 188.25,-522 242.25,-522 242.25,-558"/>
<text text-anchor="middle" x="215.25" y="-536.3" font-family="Times,serif" font-size="14.00">init</text>
</g>
<!-- denied -->
<g id="node2" class="node">
<title>denied</title>
<ellipse fill="none" stroke="black" cx="42.25" cy="-279" rx="42.49" ry="18"/>
<text text-anchor="middle" x="42.25" y="-275.3" font-family="Times,serif" font-size="14.00">denied</text>
</g>
<!-- init&#45;&gt;denied -->
<g id="edge1" class="edge">
<title>init&#45;&gt;denied</title>
<path fill="none" stroke="black" d="M188.23,-531.67C143.21,-517.82 54.16,-483.1 17.25,-417 -2.35,-381.91 14.04,-334.64 27.95,-305.79"/>
<polygon fill="black" stroke="black" points="31.12,-307.26 32.51,-296.76 24.88,-304.1 31.12,-307.26"/>
<text text-anchor="middle" x="132.25" y="-405.8" font-family="Times,serif" font-size="14.00">geolocation permanently denied</text>
</g>
<!-- getting_location -->
<g id="node3" class="node">
<title>getting_location</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-279" rx="85.29" ry="18"/>
<text text-anchor="middle" x="366.25" y="-275.3" font-family="Times,serif" font-size="14.00">getting_location</text>
</g>
<!-- init&#45;&gt;getting_location -->
<g id="edge2" class="edge">
<title>init&#45;&gt;getting_location</title>
<path fill="none" stroke="black" d="M242.41,-538.69C294.16,-537.46 403.84,-531.59 427.25,-504 481.59,-439.95 469.34,-387.69 427.25,-315 424.07,-309.52 419.68,-304.83 414.7,-300.82"/>
<polygon fill="black" stroke="black" points="416.68,-297.93 406.47,-295.09 412.67,-303.68 416.68,-297.93"/>
<text text-anchor="middle" x="559.75" y="-405.8" font-family="Times,serif" font-size="14.00">previously granted flag set</text>
</g>
<!-- idle -->
<g id="node4" class="node">
<title>idle</title>
<ellipse fill="none" stroke="black" cx="255.25" cy="-453" rx="27.9" ry="18"/>
<text text-anchor="middle" x="255.25" y="-449.3" font-family="Times,serif" font-size="14.00">idle</text>
</g>
<!-- init&#45;&gt;idle -->
<g id="edge3" class="edge">
<title>init&#45;&gt;idle</title>
<path fill="none" stroke="black" d="M212.27,-521.58C211.37,-511.6 211.62,-499.1 216.25,-489 218.98,-483.03 223.28,-477.64 228.03,-472.99"/>
<polygon fill="black" stroke="black" points="230.48,-475.49 235.73,-466.29 225.89,-470.21 230.48,-475.49"/>
<text text-anchor="middle" x="321.75" y="-492.8" font-family="Times,serif" font-size="14.00">previously granted flag unset</text>
</g>
<!-- location_found -->
<g id="node6" class="node">
<title>location_found</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-192" rx="77.99" ry="18"/>
<text text-anchor="middle" x="366.25" y="-188.3" font-family="Times,serif" font-size="14.00">location_found</text>
</g>
<!-- getting_location&#45;&gt;location_found -->
<g id="edge8" class="edge">
<title>getting_location&#45;&gt;location_found</title>
<path fill="none" stroke="black" d="M366.25,-260.8C366.25,-249.16 366.25,-233.55 366.25,-220.24"/>
<polygon fill="black" stroke="black" points="369.75,-220.18 366.25,-210.18 362.75,-220.18 369.75,-220.18"/>
<text text-anchor="middle" x="417.25" y="-231.8" font-family="Times,serif" font-size="14.00">location found</text>
</g>
<!-- request_permission -->
<g id="node5" class="node">
<title>request_permission</title>
<ellipse fill="none" stroke="black" cx="264.25" cy="-366" rx="102.08" ry="18"/>
<text text-anchor="middle" x="264.25" y="-362.3" font-family="Times,serif" font-size="14.00">request_permission</text>
</g>
<!-- idle&#45;&gt;request_permission -->
<g id="edge4" class="edge">
<title>idle&#45;&gt;request_permission</title>
<path fill="none" stroke="black" d="M282.79,-448.82C302.7,-444.93 328.29,-436.26 341.25,-417 349.95,-404.06 340.76,-393.72 326.08,-385.86"/>
<polygon fill="black" stroke="black" points="327.44,-382.63 316.9,-381.53 324.45,-388.96 327.44,-382.63"/>
<text text-anchor="middle" x="371.75" y="-405.8" font-family="Times,serif" font-size="14.00">on click</text>
</g>
<!-- request_permission&#45;&gt;denied -->
<g id="edge7" class="edge">
<title>request_permission&#45;&gt;denied</title>
<path fill="none" stroke="black" d="M202.76,-351.6C180.78,-345.99 156.06,-338.72 134.25,-330 113.26,-321.61 90.96,-309.57 73.57,-299.42"/>
<polygon fill="black" stroke="black" points="75.34,-296.4 64.96,-294.3 71.77,-302.42 75.34,-296.4"/>
<text text-anchor="middle" x="206.25" y="-318.8" font-family="Times,serif" font-size="14.00">permanently denied</text>
</g>
<!-- request_permission&#45;&gt;getting_location -->
<g id="edge5" class="edge">
<title>request_permission&#45;&gt;getting_location</title>
<path fill="none" stroke="black" d="M271.38,-348C276.49,-337.43 284.24,-324.16 294.25,-315 300.73,-309.07 308.39,-303.93 316.27,-299.56"/>
<polygon fill="black" stroke="black" points="317.92,-302.64 325.2,-294.94 314.71,-296.43 317.92,-302.64"/>
<text text-anchor="middle" x="360.75" y="-318.8" font-family="Times,serif" font-size="14.00">granted (sets flag)</text>
</g>
<!-- request_permission&#45;&gt;idle -->
<g id="edge6" class="edge">
<title>request_permission&#45;&gt;idle</title>
<path fill="none" stroke="black" d="M257.1,-384.15C255.12,-389.74 253.24,-396.04 252.25,-402 251.02,-409.35 250.95,-417.37 251.4,-424.8"/>
<polygon fill="black" stroke="black" points="247.92,-425.14 252.32,-434.78 254.89,-424.5 247.92,-425.14"/>
<text text-anchor="middle" x="294.75" y="-405.8" font-family="Times,serif" font-size="14.00">not granted</text>
</g>
<!-- open_lock -->
<g id="node7" class="node">
<title>open_lock</title>
<ellipse fill="none" stroke="black" cx="333.25" cy="-105" rx="55.79" ry="18"/>
<text text-anchor="middle" x="333.25" y="-101.3" font-family="Times,serif" font-size="14.00">open_lock</text>
</g>
<!-- location_found&#45;&gt;open_lock -->
<g id="edge9" class="edge">
<title>location_found&#45;&gt;open_lock</title>
<path fill="none" stroke="black" d="M359.57,-173.8C354.98,-161.97 348.79,-146.03 343.56,-132.58"/>
<polygon fill="black" stroke="black" points="346.68,-130.94 339.8,-122.89 340.16,-133.47 346.68,-130.94"/>
<text text-anchor="middle" x="448.25" y="-144.8" font-family="Times,serif" font-size="14.00">on click (zooms to location)</text>
</g>
<!-- open_lock&#45;&gt;location_found -->
<g id="edge10" class="edge">
<title>open_lock&#45;&gt;location_found</title>
<path fill="none" stroke="black" d="M295.44,-118.33C275.01,-127.12 256.04,-140.15 267.25,-156 273.92,-165.44 283.37,-172.35 293.8,-177.41"/>
<polygon fill="black" stroke="black" points="292.6,-180.7 303.17,-181.39 295.34,-174.26 292.6,-180.7"/>
<text text-anchor="middle" x="305.25" y="-144.8" font-family="Times,serif" font-size="14.00">after 3 sec</text>
</g>
<!-- closed_lock -->
<g id="node8" class="node">
<title>closed_lock</title>
<ellipse fill="none" stroke="black" cx="454.25" cy="-18" rx="63.09" ry="18"/>
<text text-anchor="middle" x="454.25" y="-14.3" font-family="Times,serif" font-size="14.00">closed_lock</text>
</g>
<!-- open_lock&#45;&gt;closed_lock -->
<g id="edge11" class="edge">
<title>open_lock&#45;&gt;closed_lock</title>
<path fill="none" stroke="black" d="M328.89,-87.05C327.23,-76.5 327.17,-63.24 334.25,-54 346.04,-38.59 364.24,-29.68 382.92,-24.6"/>
<polygon fill="black" stroke="black" points="383.78,-28 392.71,-22.28 382.17,-21.18 383.78,-28"/>
<text text-anchor="middle" x="447.75" y="-57.8" font-family="Times,serif" font-size="14.00">on click (locks zoom to location)</text>
</g>
<!-- closed_lock&#45;&gt;location_found -->
<g id="edge12" class="edge">
<title>closed_lock&#45;&gt;location_found</title>
<path fill="none" stroke="black" d="M513.08,-24.74C531.5,-29.64 549.95,-38.41 561.25,-54 588.04,-90.95 580.67,-122.9 549.25,-156 535.31,-170.68 491.24,-179.37 449.85,-184.42"/>
<polygon fill="black" stroke="black" points="449.22,-180.97 439.68,-185.6 450.02,-187.93 449.22,-180.97"/>
<text text-anchor="middle" x="604.75" y="-101.3" font-family="Times,serif" font-size="14.00">on click</text>
</g>
</g>
viewBox="0.00 0.00 664.25 566.00" xmlns="http://www.w3.org/2000/svg">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 562)">
<title>G</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-562 660.25,-562 660.25,4 -4,4"/>
<!-- init -->
<g id="node1" class="node">
<title>init</title>
<polygon fill="none" stroke="black" points="242.25,-558 188.25,-558 188.25,-522 242.25,-522 242.25,-558"/>
<text text-anchor="middle" x="215.25" y="-536.3" font-family="Times,serif" font-size="14.00">init</text>
</g>
<!-- denied -->
<g id="node2" class="node">
<title>denied</title>
<ellipse fill="none" stroke="black" cx="42.25" cy="-279" rx="42.49" ry="18"/>
<text text-anchor="middle" x="42.25" y="-275.3" font-family="Times,serif" font-size="14.00">denied</text>
</g>
<!-- init&#45;&gt;denied -->
<g id="edge1" class="edge">
<title>init&#45;&gt;denied</title>
<path fill="none" stroke="black"
d="M188.23,-531.67C143.21,-517.82 54.16,-483.1 17.25,-417 -2.35,-381.91 14.04,-334.64 27.95,-305.79"/>
<polygon fill="black" stroke="black" points="31.12,-307.26 32.51,-296.76 24.88,-304.1 31.12,-307.26"/>
<text text-anchor="middle" x="132.25" y="-405.8" font-family="Times,serif" font-size="14.00">geolocation
permanently denied
</text>
</g>
<!-- getting_location -->
<g id="node3" class="node">
<title>getting_location</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-279" rx="85.29" ry="18"/>
<text text-anchor="middle" x="366.25" y="-275.3" font-family="Times,serif" font-size="14.00">
getting_location
</text>
</g>
<!-- init&#45;&gt;getting_location -->
<g id="edge2" class="edge">
<title>init&#45;&gt;getting_location</title>
<path fill="none" stroke="black"
d="M242.41,-538.69C294.16,-537.46 403.84,-531.59 427.25,-504 481.59,-439.95 469.34,-387.69 427.25,-315 424.07,-309.52 419.68,-304.83 414.7,-300.82"/>
<polygon fill="black" stroke="black" points="416.68,-297.93 406.47,-295.09 412.67,-303.68 416.68,-297.93"/>
<text text-anchor="middle" x="559.75" y="-405.8" font-family="Times,serif" font-size="14.00">previously
granted flag set
</text>
</g>
<!-- idle -->
<g id="node4" class="node">
<title>idle</title>
<ellipse fill="none" stroke="black" cx="255.25" cy="-453" rx="27.9" ry="18"/>
<text text-anchor="middle" x="255.25" y="-449.3" font-family="Times,serif" font-size="14.00">idle</text>
</g>
<!-- init&#45;&gt;idle -->
<g id="edge3" class="edge">
<title>init&#45;&gt;idle</title>
<path fill="none" stroke="black"
d="M212.27,-521.58C211.37,-511.6 211.62,-499.1 216.25,-489 218.98,-483.03 223.28,-477.64 228.03,-472.99"/>
<polygon fill="black" stroke="black" points="230.48,-475.49 235.73,-466.29 225.89,-470.21 230.48,-475.49"/>
<text text-anchor="middle" x="321.75" y="-492.8" font-family="Times,serif" font-size="14.00">previously
granted flag unset
</text>
</g>
<!-- location_found -->
<g id="node6" class="node">
<title>location_found</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-192" rx="77.99" ry="18"/>
<text text-anchor="middle" x="366.25" y="-188.3" font-family="Times,serif" font-size="14.00">
location_found
</text>
</g>
<!-- getting_location&#45;&gt;location_found -->
<g id="edge8" class="edge">
<title>getting_location&#45;&gt;location_found</title>
<path fill="none" stroke="black" d="M366.25,-260.8C366.25,-249.16 366.25,-233.55 366.25,-220.24"/>
<polygon fill="black" stroke="black" points="369.75,-220.18 366.25,-210.18 362.75,-220.18 369.75,-220.18"/>
<text text-anchor="middle" x="417.25" y="-231.8" font-family="Times,serif" font-size="14.00">location
found
</text>
</g>
<!-- request_permission -->
<g id="node5" class="node">
<title>request_permission</title>
<ellipse fill="none" stroke="black" cx="264.25" cy="-366" rx="102.08" ry="18"/>
<text text-anchor="middle" x="264.25" y="-362.3" font-family="Times,serif" font-size="14.00">
request_permission
</text>
</g>
<!-- idle&#45;&gt;request_permission -->
<g id="edge4" class="edge">
<title>idle&#45;&gt;request_permission</title>
<path fill="none" stroke="black"
d="M282.79,-448.82C302.7,-444.93 328.29,-436.26 341.25,-417 349.95,-404.06 340.76,-393.72 326.08,-385.86"/>
<polygon fill="black" stroke="black" points="327.44,-382.63 316.9,-381.53 324.45,-388.96 327.44,-382.63"/>
<text text-anchor="middle" x="371.75" y="-405.8" font-family="Times,serif" font-size="14.00">on click</text>
</g>
<!-- request_permission&#45;&gt;denied -->
<g id="edge7" class="edge">
<title>request_permission&#45;&gt;denied</title>
<path fill="none" stroke="black"
d="M202.76,-351.6C180.78,-345.99 156.06,-338.72 134.25,-330 113.26,-321.61 90.96,-309.57 73.57,-299.42"/>
<polygon fill="black" stroke="black" points="75.34,-296.4 64.96,-294.3 71.77,-302.42 75.34,-296.4"/>
<text text-anchor="middle" x="206.25" y="-318.8" font-family="Times,serif" font-size="14.00">permanently
denied
</text>
</g>
<!-- request_permission&#45;&gt;getting_location -->
<g id="edge5" class="edge">
<title>request_permission&#45;&gt;getting_location</title>
<path fill="none" stroke="black"
d="M271.38,-348C276.49,-337.43 284.24,-324.16 294.25,-315 300.73,-309.07 308.39,-303.93 316.27,-299.56"/>
<polygon fill="black" stroke="black" points="317.92,-302.64 325.2,-294.94 314.71,-296.43 317.92,-302.64"/>
<text text-anchor="middle" x="360.75" y="-318.8" font-family="Times,serif" font-size="14.00">granted (sets
flag)
</text>
</g>
<!-- request_permission&#45;&gt;idle -->
<g id="edge6" class="edge">
<title>request_permission&#45;&gt;idle</title>
<path fill="none" stroke="black"
d="M257.1,-384.15C255.12,-389.74 253.24,-396.04 252.25,-402 251.02,-409.35 250.95,-417.37 251.4,-424.8"/>
<polygon fill="black" stroke="black" points="247.92,-425.14 252.32,-434.78 254.89,-424.5 247.92,-425.14"/>
<text text-anchor="middle" x="294.75" y="-405.8" font-family="Times,serif" font-size="14.00">not granted
</text>
</g>
<!-- open_lock -->
<g id="node7" class="node">
<title>open_lock</title>
<ellipse fill="none" stroke="black" cx="333.25" cy="-105" rx="55.79" ry="18"/>
<text text-anchor="middle" x="333.25" y="-101.3" font-family="Times,serif" font-size="14.00">open_lock
</text>
</g>
<!-- location_found&#45;&gt;open_lock -->
<g id="edge9" class="edge">
<title>location_found&#45;&gt;open_lock</title>
<path fill="none" stroke="black" d="M359.57,-173.8C354.98,-161.97 348.79,-146.03 343.56,-132.58"/>
<polygon fill="black" stroke="black" points="346.68,-130.94 339.8,-122.89 340.16,-133.47 346.68,-130.94"/>
<text text-anchor="middle" x="448.25" y="-144.8" font-family="Times,serif" font-size="14.00">on click (zooms
to location)
</text>
</g>
<!-- open_lock&#45;&gt;location_found -->
<g id="edge10" class="edge">
<title>open_lock&#45;&gt;location_found</title>
<path fill="none" stroke="black"
d="M295.44,-118.33C275.01,-127.12 256.04,-140.15 267.25,-156 273.92,-165.44 283.37,-172.35 293.8,-177.41"/>
<polygon fill="black" stroke="black" points="292.6,-180.7 303.17,-181.39 295.34,-174.26 292.6,-180.7"/>
<text text-anchor="middle" x="305.25" y="-144.8" font-family="Times,serif" font-size="14.00">after 3 sec
</text>
</g>
<!-- closed_lock -->
<g id="node8" class="node">
<title>closed_lock</title>
<ellipse fill="none" stroke="black" cx="454.25" cy="-18" rx="63.09" ry="18"/>
<text text-anchor="middle" x="454.25" y="-14.3" font-family="Times,serif" font-size="14.00">closed_lock
</text>
</g>
<!-- open_lock&#45;&gt;closed_lock -->
<g id="edge11" class="edge">
<title>open_lock&#45;&gt;closed_lock</title>
<path fill="none" stroke="black"
d="M328.89,-87.05C327.23,-76.5 327.17,-63.24 334.25,-54 346.04,-38.59 364.24,-29.68 382.92,-24.6"/>
<polygon fill="black" stroke="black" points="383.78,-28 392.71,-22.28 382.17,-21.18 383.78,-28"/>
<text text-anchor="middle" x="447.75" y="-57.8" font-family="Times,serif" font-size="14.00">on click (locks
zoom to location)
</text>
</g>
<!-- closed_lock&#45;&gt;location_found -->
<g id="edge12" class="edge">
<title>closed_lock&#45;&gt;location_found</title>
<path fill="none" stroke="black"
d="M513.08,-24.74C531.5,-29.64 549.95,-38.41 561.25,-54 588.04,-90.95 580.67,-122.9 549.25,-156 535.31,-170.68 491.24,-179.37 449.85,-184.42"/>
<polygon fill="black" stroke="black" points="449.22,-180.97 439.68,-185.6 450.02,-187.93 449.22,-180.97"/>
<text text-anchor="middle" x="604.75" y="-101.3" font-family="Times,serif" font-size="14.00">on click</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -6,13 +6,15 @@ Some highlights of new releases.
0.10
----
The 0.10 version contains a lot of refactorings on various core of the application, namely in the rendering stack, the fetching of data and uploading.
The 0.10 version contains a lot of refactorings on various core of the application, namely in the rendering stack, the
fetching of data and uploading.
Some highlights are:
1. The addition of fallback overpass servers
2. Fetching data from OSM directly (especially useful in the personal theme)
3. Splitting all the features per tile (with a maximum amount of features per tile, splitting further if needed), making everything a ton faster
3. Splitting all the features per tile (with a maximum amount of features per tile, splitting further if needed), making
everything a ton faster
4. If a tile has too much features, the featuers are not shown. Instead, a rectangle with the feature amount is shown.
Furthermore, it contains a few new themes and theme updates:
@ -31,9 +33,8 @@ Other various small improvements:
0.8 and 0.9
-----------
Addition of filters per layer
Addition of a download-as-pdf for select themes
Addition of a download-as-geojson and download-as-csv for select themes
Addition of filters per layer Addition of a download-as-pdf for select themes Addition of a download-as-geojson and
download-as-csv for select themes
...

View file

@ -0,0 +1,39 @@
{
"$ref": "#/definitions/AndOrTagConfigJson",
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,37 @@
export default {
"$ref": "#/definitions/AndOrTagConfigJson",
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,93 @@
{
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,91 @@
export default {
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,72 @@
{
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,70 @@
export default {
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,900 @@
{
"description": "Configuration for a single layer",
"type": "object",
"properties": {
"id": {
"description": "The id of this layer.\nThis should be a simple, lowercase, human readable string that is used to identify the layer.",
"type": "string"
},
"name": {
"description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control"
},
"description": {
"description": "A description for this layer.\nShown in the layer selections and in the personel theme"
},
"source": {
"description": "This determines where the data for the layer is fetched.\nThere are some options:\n\n# Query OSM directly\nsource: {osmTags: \"key=value\"}\n will fetch all objects with given tags from OSM.\n Currently, this will create a query to overpass and fetch the data - in the future this might fetch from the OSM API\n\n# Query OSM Via the overpass API with a custom script\nsource: {overpassScript: \"<custom overpass tags>\"} when you want to do special things. _This should be really rare_.\n This means that the data will be pulled from overpass with this script, and will ignore the osmTags for the query\n However, for the rest of the pipeline, the OsmTags will _still_ be used. This is important to enable layers etc...\n\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}\nSome API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nNote that both geojson-options might set a flag 'isOsmCache' indicating that the data originally comes from OSM too\n\n\nNOTE: the previous format was 'overpassTags: AndOrTagConfigJson | string', which is interpreted as a shorthand for source: {osmTags: \"key=value\"}\n While still supported, this is considered deprecated",
"anyOf": [
{
"allOf": [
{
"type": "object",
"properties": {
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"overpassScript": {
"type": "string"
}
},
"required": [
"osmTags"
]
},
{
"type": "object",
"properties": {
"maxCacheAge": {
"description": "The maximum amount of seconds that a tile is allowed to linger in the cache",
"type": "number"
}
}
}
]
},
{
"allOf": [
{
"type": "object",
"properties": {
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"geoJson": {
"type": "string"
},
"geoJsonZoomLevel": {
"type": "number"
},
"isOsmCache": {
"type": "boolean"
},
"mercatorCrs": {
"type": "boolean"
}
},
"required": [
"geoJson",
"osmTags"
]
},
{
"type": "object",
"properties": {
"maxCacheAge": {
"description": "The maximum amount of seconds that a tile is allowed to linger in the cache",
"type": "number"
}
}
}
]
}
]
},
"calculatedTags": {
"description": "A list of extra tags to calculate, specified as \"keyToAssignTo=javascript-expression\".\nThere are a few extra functions available. Refer to <a>Docs/CalculatedTags.md</a> for more information\nThe functions will be run in order, e.g.\n[\n \"_max_overlap_m2=Math.max(...feat.overlapsWith(\"someOtherLayer\").map(o => o.overlap))\n \"_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area\n]",
"type": "array",
"items": {
"type": "string"
}
},
"doNotDownload": {
"description": "If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.\nWorks well together with 'passAllFeatures', to add decoration",
"type": "boolean"
},
"isShown": {
"description": "This tag rendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view.\nThis is useful to hide certain features from view.\n\nImportant: hiding features does not work dynamically, but is only calculated when the data is first renders.\nThis implies that it is not possible to hide a feature after a tagging change\n\nThe default value is 'yes'",
"$ref": "#/definitions/TagRenderingConfigJson"
},
"minzoom": {
"description": "The minimum needed zoomlevel required before loading of the data start\nDefault: 0",
"type": "number"
},
"minzoomVisible": {
"description": "The zoom level at which point the data is hidden again\nDefault: 100 (thus: always visible",
"type": "number"
},
"title": {
"description": "The title shown in a popup for elements of this layer.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"titleIcons": {
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons",
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"mapRendering": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/default_3"
},
{
"$ref": "#/definitions/default_4"
}
]
}
},
"passAllFeatures": {
"description": "If set, this layer will pass all the features it receives onto the next layer.\nThis is ideal for decoration, e.g. directionss on cameras",
"type": "boolean"
},
"presets": {
"description": "Presets for this layer.\nA preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);\nit will prompt the user to add a new point.\n\nThe most important aspect are the tags, which define which tags the new point will have;\nThe title is shown in the dialog, along with the first sentence of the description.\n\nUpon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.\n\nNote: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!\nNB: if no presets are defined, the popup to add new points doesn't show up at all",
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"description": "The title - shown on the 'add-new'-button."
},
"tags": {
"description": "The tags to add. It determines the icon too",
"type": "array",
"items": {
"type": "string"
}
},
"description": {
"description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)"
},
"preciseInput": {
"description": "If set, the user will prompted to confirm the location before actually adding the data.\nThis will be with a 'drag crosshair'-method.\n\nIf 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category.",
"anyOf": [
{
"type": "object",
"properties": {
"preferredBackground": {
"description": "The type of background picture",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"snapToLayer": {
"description": "If specified, these layers will be shown to and the new point will be snapped towards it",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"maxSnapDistance": {
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number"
}
},
"required": [
"preferredBackground"
]
},
{
"enum": [
true
],
"type": "boolean"
}
]
}
},
"required": [
"tags",
"title"
]
}
},
"tagRenderings": {
"description": "All the tag renderings.\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together",
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "object",
"properties": {
"builtin": {
"type": "string"
},
"override": {}
},
"required": [
"builtin",
"override"
]
},
{
"type": "object",
"properties": {
"rewrite": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sourceString": {
"type": "string"
},
"into": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"into",
"sourceString"
]
}
},
"renderings": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "object",
"properties": {
"builtin": {
"type": "string"
},
"override": {}
},
"required": [
"builtin",
"override"
]
},
{
"type": "string"
}
]
}
}
},
"required": [
"renderings",
"rewrite"
]
},
{
"type": "string"
}
]
}
},
"filter": {
"description": "All the extra questions for filtering",
"type": "array",
"items": {
"$ref": "#/definitions/default"
}
},
"deletion": {
"description": "This block defines under what circumstances the delete dialog is shown for objects of this layer.\nIf set, a dialog is shown to the user to (soft) delete the point.\nThe dialog is built to be user friendly and to prevent mistakes.\nIf deletion is not possible, the dialog will hide itself and show the reason of non-deletability instead.\n\nTo configure, the following values are possible:\n\n- false: never ever show the delete button\n- true: show the default delete button\n- undefined: use the mapcomplete default to show deletion or not. Currently, this is the same as 'false' but this will change in the future\n- or: a hash with options (see below)\n\n The delete dialog\n =================\n\n\n\n#### Hard deletion if enough experience\n\nA feature can only be deleted from OpenStreetMap by mapcomplete if:\n\n- It is a node\n- No ways or relations use the node\n- The logged-in user has enough experience OR the user is the only one to have edited the point previously\n- The logged-in user has no unread messages (or has a ton of experience)\n- The user did not select one of the 'non-delete-options' (see below)\n\nIn all other cases, a 'soft deletion' is used.\n\n#### Soft deletion\n\nA 'soft deletion' is when the point isn't deleted from OSM but retagged so that it'll won't how up in the mapcomplete theme anymore.\nThis makes it look like it was deleted, without doing damage. A fixme will be added to the point.\n\nNote that a soft deletion is _only_ possible if these tags are provided by the theme creator, as they'll be different for every theme\n\n#### No-delete options\n\nIn some cases, the contributor might want to delete something for the wrong reason (e.g. someone who wants to have a path removed \"because the path is on their private property\").\nHowever, the path exists in reality and should thus be on OSM - otherwise the next contributor will pass by and notice \"hey, there is a path missing here! Let me redraw it in OSM!)\n\nThe correct approach is to retag the feature in such a way that it is semantically correct *and* that it doesn't show up on the theme anymore.\nA no-delete option is offered as 'reason to delete it', but secretly retags.",
"anyOf": [
{
"$ref": "#/definitions/DeleteConfigJson"
},
{
"type": "boolean"
}
]
},
"allowMove": {
"description": "Indicates if a point can be moved and configures the modalities.\n\nA feature can be moved by MapComplete if:\n\n- It is a point\n- The point is _not_ part of a way or a a relation.\n\nOff by default. Can be enabled by setting this flag or by configuring.",
"anyOf": [
{
"$ref": "#/definitions/default_2"
},
{
"type": "boolean"
}
]
},
"allowSplit": {
"description": "IF set, a 'split this road' button is shown",
"type": "boolean"
},
"units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array",
"items": {
"$ref": "#/definitions/default_1"
}
}
},
"required": [
"id",
"mapRendering",
"source"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"additionalProperties": false
},
"default_3": {
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
],
"additionalProperties": false
},
"default_4": {
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
},
"additionalProperties": false
},
"default": {
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
],
"additionalProperties": false
},
"DeleteConfigJson": {
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
},
"additionalProperties": false
},
"default_2": {
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
},
"additionalProperties": false
},
"default_1": {
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,890 @@
export default {
"description": "Configuration for a single layer",
"type": "object",
"properties": {
"id": {
"description": "The id of this layer.\nThis should be a simple, lowercase, human readable string that is used to identify the layer.",
"type": "string"
},
"name": {
"description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control"
},
"description": {
"description": "A description for this layer.\nShown in the layer selections and in the personel theme"
},
"source": {
"description": "This determines where the data for the layer is fetched.\nThere are some options:\n\n# Query OSM directly\nsource: {osmTags: \"key=value\"}\n will fetch all objects with given tags from OSM.\n Currently, this will create a query to overpass and fetch the data - in the future this might fetch from the OSM API\n\n# Query OSM Via the overpass API with a custom script\nsource: {overpassScript: \"<custom overpass tags>\"} when you want to do special things. _This should be really rare_.\n This means that the data will be pulled from overpass with this script, and will ignore the osmTags for the query\n However, for the rest of the pipeline, the OsmTags will _still_ be used. This is important to enable layers etc...\n\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}\nSome API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nNote that both geojson-options might set a flag 'isOsmCache' indicating that the data originally comes from OSM too\n\n\nNOTE: the previous format was 'overpassTags: AndOrTagConfigJson | string', which is interpreted as a shorthand for source: {osmTags: \"key=value\"}\n While still supported, this is considered deprecated",
"anyOf": [
{
"allOf": [
{
"type": "object",
"properties": {
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"overpassScript": {
"type": "string"
}
},
"required": [
"osmTags"
]
},
{
"type": "object",
"properties": {
"maxCacheAge": {
"description": "The maximum amount of seconds that a tile is allowed to linger in the cache",
"type": "number"
}
}
}
]
},
{
"allOf": [
{
"type": "object",
"properties": {
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"geoJson": {
"type": "string"
},
"geoJsonZoomLevel": {
"type": "number"
},
"isOsmCache": {
"type": "boolean"
},
"mercatorCrs": {
"type": "boolean"
}
},
"required": [
"geoJson",
"osmTags"
]
},
{
"type": "object",
"properties": {
"maxCacheAge": {
"description": "The maximum amount of seconds that a tile is allowed to linger in the cache",
"type": "number"
}
}
}
]
}
]
},
"calculatedTags": {
"description": "A list of extra tags to calculate, specified as \"keyToAssignTo=javascript-expression\".\nThere are a few extra functions available. Refer to <a>Docs/CalculatedTags.md</a> for more information\nThe functions will be run in order, e.g.\n[\n \"_max_overlap_m2=Math.max(...feat.overlapsWith(\"someOtherLayer\").map(o => o.overlap))\n \"_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area\n]",
"type": "array",
"items": {
"type": "string"
}
},
"doNotDownload": {
"description": "If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.\nWorks well together with 'passAllFeatures', to add decoration",
"type": "boolean"
},
"isShown": {
"description": "This tag rendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view.\nThis is useful to hide certain features from view.\n\nImportant: hiding features does not work dynamically, but is only calculated when the data is first renders.\nThis implies that it is not possible to hide a feature after a tagging change\n\nThe default value is 'yes'",
"$ref": "#/definitions/TagRenderingConfigJson"
},
"minzoom": {
"description": "The minimum needed zoomlevel required before loading of the data start\nDefault: 0",
"type": "number"
},
"minzoomVisible": {
"description": "The zoom level at which point the data is hidden again\nDefault: 100 (thus: always visible",
"type": "number"
},
"title": {
"description": "The title shown in a popup for elements of this layer.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"titleIcons": {
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons",
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"mapRendering": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/default_3"
},
{
"$ref": "#/definitions/default_4"
}
]
}
},
"passAllFeatures": {
"description": "If set, this layer will pass all the features it receives onto the next layer.\nThis is ideal for decoration, e.g. directionss on cameras",
"type": "boolean"
},
"presets": {
"description": "Presets for this layer.\nA preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);\nit will prompt the user to add a new point.\n\nThe most important aspect are the tags, which define which tags the new point will have;\nThe title is shown in the dialog, along with the first sentence of the description.\n\nUpon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.\n\nNote: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!\nNB: if no presets are defined, the popup to add new points doesn't show up at all",
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"description": "The title - shown on the 'add-new'-button."
},
"tags": {
"description": "The tags to add. It determines the icon too",
"type": "array",
"items": {
"type": "string"
}
},
"description": {
"description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)"
},
"preciseInput": {
"description": "If set, the user will prompted to confirm the location before actually adding the data.\nThis will be with a 'drag crosshair'-method.\n\nIf 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category.",
"anyOf": [
{
"type": "object",
"properties": {
"preferredBackground": {
"description": "The type of background picture",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"snapToLayer": {
"description": "If specified, these layers will be shown to and the new point will be snapped towards it",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"maxSnapDistance": {
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number"
}
},
"required": [
"preferredBackground"
]
},
{
"enum": [
true
],
"type": "boolean"
}
]
}
},
"required": [
"tags",
"title"
]
}
},
"tagRenderings": {
"description": "All the tag renderings.\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together",
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "object",
"properties": {
"builtin": {
"type": "string"
},
"override": {}
},
"required": [
"builtin",
"override"
]
},
{
"type": "object",
"properties": {
"rewrite": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sourceString": {
"type": "string"
},
"into": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"into",
"sourceString"
]
}
},
"renderings": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "object",
"properties": {
"builtin": {
"type": "string"
},
"override": {}
},
"required": [
"builtin",
"override"
]
},
{
"type": "string"
}
]
}
}
},
"required": [
"renderings",
"rewrite"
]
},
{
"type": "string"
}
]
}
},
"filter": {
"description": "All the extra questions for filtering",
"type": "array",
"items": {
"$ref": "#/definitions/default"
}
},
"deletion": {
"description": "This block defines under what circumstances the delete dialog is shown for objects of this layer.\nIf set, a dialog is shown to the user to (soft) delete the point.\nThe dialog is built to be user friendly and to prevent mistakes.\nIf deletion is not possible, the dialog will hide itself and show the reason of non-deletability instead.\n\nTo configure, the following values are possible:\n\n- false: never ever show the delete button\n- true: show the default delete button\n- undefined: use the mapcomplete default to show deletion or not. Currently, this is the same as 'false' but this will change in the future\n- or: a hash with options (see below)\n\n The delete dialog\n =================\n\n\n\n#### Hard deletion if enough experience\n\nA feature can only be deleted from OpenStreetMap by mapcomplete if:\n\n- It is a node\n- No ways or relations use the node\n- The logged-in user has enough experience OR the user is the only one to have edited the point previously\n- The logged-in user has no unread messages (or has a ton of experience)\n- The user did not select one of the 'non-delete-options' (see below)\n\nIn all other cases, a 'soft deletion' is used.\n\n#### Soft deletion\n\nA 'soft deletion' is when the point isn't deleted from OSM but retagged so that it'll won't how up in the mapcomplete theme anymore.\nThis makes it look like it was deleted, without doing damage. A fixme will be added to the point.\n\nNote that a soft deletion is _only_ possible if these tags are provided by the theme creator, as they'll be different for every theme\n\n#### No-delete options\n\nIn some cases, the contributor might want to delete something for the wrong reason (e.g. someone who wants to have a path removed \"because the path is on their private property\").\nHowever, the path exists in reality and should thus be on OSM - otherwise the next contributor will pass by and notice \"hey, there is a path missing here! Let me redraw it in OSM!)\n\nThe correct approach is to retag the feature in such a way that it is semantically correct *and* that it doesn't show up on the theme anymore.\nA no-delete option is offered as 'reason to delete it', but secretly retags.",
"anyOf": [
{
"$ref": "#/definitions/DeleteConfigJson"
},
{
"type": "boolean"
}
]
},
"allowMove": {
"description": "Indicates if a point can be moved and configures the modalities.\n\nA feature can be moved by MapComplete if:\n\n- It is a point\n- The point is _not_ part of a way or a a relation.\n\nOff by default. Can be enabled by setting this flag or by configuring.",
"anyOf": [
{
"$ref": "#/definitions/default_2"
},
{
"type": "boolean"
}
]
},
"allowSplit": {
"description": "IF set, a 'split this road' button is shown",
"type": "boolean"
},
"units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array",
"items": {
"$ref": "#/definitions/default_1"
}
}
},
"required": [
"id",
"mapRendering",
"source"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
}
},
"default_3": {
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
]
},
"default_4": {
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
}
},
"default": {
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
]
},
"DeleteConfigJson": {
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
}
},
"default_2": {
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
}
},
"default_1": {
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
]
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,293 @@
{
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,289 @@
export default {
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,87 @@
{
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,84 @@
export default {
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,305 @@
{
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,301 @@
export default {
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,167 @@
{
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,165 @@
export default {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,569 @@
{
"description": "Configuration for a tilesource config",
"type": "object",
"properties": {
"id": {
"description": "Id of this overlay, used in the URL-parameters to set the state",
"type": "string"
},
"source": {
"description": "The path, where {x}, {y} and {z} will be substituted",
"type": "string"
},
"isOverlay": {
"description": "Wether or not this is an overlay. Default: true",
"type": "boolean"
},
"name": {
"description": "How this will be shown in the selection menu.\nMake undefined if this may not be toggled"
},
"minZoom": {
"description": "Only visible at this or a higher zoom level",
"type": "number"
},
"maxZoom": {
"description": "Only visible at this or a lower zoom level",
"type": "number"
},
"defaultState": {
"description": "The default state, set to false to hide by default",
"type": "boolean"
}
},
"required": [
"defaultState",
"id",
"source"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
},
"additionalProperties": false
},
"default_3": {
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
],
"additionalProperties": false
},
"default_4": {
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
},
"additionalProperties": false
},
"default": {
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
],
"additionalProperties": false
},
"DeleteConfigJson": {
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
},
"additionalProperties": false
},
"default_2": {
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
},
"additionalProperties": false
},
"default_1": {
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,559 @@
export default {
"description": "Configuration for a tilesource config",
"type": "object",
"properties": {
"id": {
"description": "Id of this overlay, used in the URL-parameters to set the state",
"type": "string"
},
"source": {
"description": "The path, where {x}, {y} and {z} will be substituted",
"type": "string"
},
"isOverlay": {
"description": "Wether or not this is an overlay. Default: true",
"type": "boolean"
},
"name": {
"description": "How this will be shown in the selection menu.\nMake undefined if this may not be toggled"
},
"minZoom": {
"description": "Only visible at this or a higher zoom level",
"type": "number"
},
"maxZoom": {
"description": "Only visible at this or a lower zoom level",
"type": "number"
},
"defaultState": {
"description": "The default state, set to false to hide by default",
"type": "boolean"
}
},
"required": [
"defaultState",
"id",
"source"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
},
"TagRenderingConfigJson": {
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead.",
"type": "object",
"properties": {
"id": {
"description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise",
"type": "string"
},
"group": {
"description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.",
"type": "string"
},
"render": {
"description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`"
},
"question": {
"description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only"
},
"condition": {
"description": "Only show this question if the object also matches the following tags.\n\nThis is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"freeform": {
"description": "Allow freeform text input from the user",
"type": "object",
"properties": {
"key": {
"description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown",
"type": "string"
},
"type": {
"description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values",
"type": "string"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
"type": "array",
"items": {}
},
"addExtraTags": {
"description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'",
"type": "array",
"items": {
"type": "string"
}
},
"inline": {
"description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.",
"type": "boolean"
},
"default": {
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
"type": "string"
}
},
"required": [
"key"
]
},
"multiAnswer": {
"description": "If true, use checkboxes instead of radio buttons when asking the question",
"type": "boolean"
},
"mappings": {
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option"
},
"hideInAnswer": {
"description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": [
"string",
"boolean"
]
}
]
},
"ifnot": {
"description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"addExtraTags": {
"description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"if",
"then"
]
}
}
}
},
"default_3": {
"description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way",
"type": "object",
"properties": {
"location": {
"description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint",
"type": "array",
"items": {
"enum": [
"centroid",
"end",
"point",
"start"
],
"type": "string"
}
},
"icon": {
"description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"iconBadges": {
"description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"then": {
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"if",
"then"
]
}
},
"iconSize": {
"description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"rotation": {
"description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"label": {
"description": "A HTML-fragment that is shown below the icon, for example:\n<div style=\"background: white; display: block\">{name}</div>\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well.",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"location"
]
},
"default_4": {
"description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area",
"type": "object",
"properties": {
"color": {
"description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"width": {
"description": "The stroke-width for way-elements",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"dashArray": {
"description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"lineCap": {
"description": "The form at the end of a line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"fill": {
"description": "Wehter or not to fill polygons",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"enum": [
"no",
"yes"
],
"type": "string"
}
]
},
"fillColor": {
"description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"offset": {
"description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "number"
}
]
}
}
},
"default": {
"type": "object",
"properties": {
"id": {
"description": "An id/name for this filter, used to set the URL parameters",
"type": "string"
},
"options": {
"description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.",
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {},
"osmTags": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"required": [
"question"
]
}
}
},
"required": [
"id",
"options"
]
},
"DeleteConfigJson": {
"type": "object",
"properties": {
"extraDeleteReasons": {
"description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from",
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {
"description": "The text that will be shown to the user - translatable"
},
"changesetMessage": {
"description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english",
"type": "string"
}
},
"required": [
"changesetMessage",
"explanation"
]
}
},
"nonDeleteMappings": {
"description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!",
"type": "array",
"items": {
"type": "object",
"properties": {
"if": {
"$ref": "#/definitions/AndOrTagConfigJson"
},
"then": {}
},
"required": [
"if",
"then"
]
}
},
"softDeletionTags": {
"description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```",
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
},
"neededChangesets": {
"description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here.",
"type": "number"
}
}
},
"default_2": {
"type": "object",
"properties": {
"enableImproveAccuracy": {
"description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason",
"type": "boolean"
},
"enableRelocation": {
"description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason",
"type": "boolean"
}
}
},
"default_1": {
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
]
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,101 @@
{
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
},
"additionalProperties": false
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}

View file

@ -0,0 +1,98 @@
export default {
"type": "object",
"properties": {
"appliesToKey": {
"description": "Every key from this list will be normalized",
"type": "array",
"items": {
"type": "string"
}
},
"eraseInvalidValues": {
"description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this",
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicableUnitJson"
}
}
},
"required": [
"applicableUnits",
"appliesToKey"
],
"definitions": {
"AndOrTagConfigJson": {
"type": "object",
"properties": {
"and": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
},
"or": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/AndOrTagConfigJson"
},
{
"type": "string"
}
]
}
}
}
},
"ApplicableUnitJson": {
"type": "object",
"properties": {
"canonicalDenomination": {
"description": "The canonical value which will be added to the text.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'",
"type": "string"
},
"canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'",
"type": "string"
},
"alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.",
"type": "array",
"items": {
"type": "string"
}
},
"human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}"
},
"humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}"
},
"prefix": {
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field",
"type": "boolean"
},
"default": {
"description": "The default interpretation - only one can be set.\nIf none is set, the first unit will be considered the default interpretation of a value without a unit",
"type": "boolean"
}
},
"required": [
"canonicalDenomination"
]
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -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

View file

@ -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

View file

@ -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')",

View file

@ -543,129 +543,129 @@
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"value": ""
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"value": ""
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"value": ""
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"value": ""
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"value": ""
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"value": ""
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"value": ""
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "broken"
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"value": ""
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"value": ""
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"value": ""
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"value": ""
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"value": ""
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"value": ""
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"value": ""
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"value": ""
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"value": ""
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"value": ""
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"value": ""
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.",
"value": ""
},
{
"key": "planned:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.",
"value": ""
},
{
"key": "construction:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.",
"value": ""
},
{
"key": "disused:amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.",
"value": ""
},
{
"key": "operational_status",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.",
"value": ""
},
{
"key": "amenity",
"description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",
"value": "charging_station"
},
{
"key": "parking:fee",
"description": "Layer 'Charging stations' shows parking:fee=no with a fixed text, namely 'No additional parking cost while charging' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')",

View file

@ -600,7 +600,7 @@
},
{
"key": "cycle_barrier:type",
"description": "Layer 'Barriers' shows cycle_barrier:type=double with a fixed text, namely 'Double, two barriers behind each other <img src='./assets/themes/cycle_infra/Cycle_barrier_double.png' style='width:8em'>' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Bicycle infrastructure')",
"description": "Layer 'Barriers' shows cycle_barrier:type=double with a fixed text, namely 'Double, two barriers behind each other <img src='./assets/themes/cycle_infra/Cycle_barrier_double.svg' style='width:8em'>' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Bicycle infrastructure')",
"value": "double"
},
{

View file

@ -966,13 +966,13 @@
},
{
"key": "location",
"description": "Layer 'Bike parking' shows location=underground with a fixed text, namely 'Underground 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 '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"
"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",

View file

@ -76,16 +76,6 @@
"description": "Layer 'Restaurants and fast food' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
"value": "yes"
},
{
"key": "payment:app",
"description": "Layer 'Restaurants and fast food' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
"value": "yes"
},
{
"key": "payment:membership_card",
"description": "Layer 'Restaurants and fast food' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
"value": "yes"
},
{
"key": "wheelchair",
"description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')",
@ -315,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')",

View file

@ -81,16 +81,6 @@
"description": "Layer 'Fries shop' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "payment:app",
"description": "Layer 'Fries shop' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "payment:membership_card",
"description": "Layer 'Fries shop' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "wheelchair",
"description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
@ -320,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')",
@ -406,16 +416,6 @@
"description": "Layer 'Restaurants and fast food' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "payment:app",
"description": "Layer 'Restaurants and fast food' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "payment:membership_card",
"description": "Layer 'Restaurants and fast food' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
"value": "yes"
},
{
"key": "wheelchair",
"description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')",
@ -645,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')",

View file

@ -76,16 +76,6 @@
"description": "Layer 'Observation towers' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Observation towers')",
"value": "yes"
},
{
"key": "payment:app",
"description": "Layer 'Observation towers' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Observation towers')",
"value": "yes"
},
{
"key": "payment:membership_card",
"description": "Layer 'Observation towers' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Observation towers')",
"value": "yes"
},
{
"key": "wheelchair",
"description": "Layer 'Observation towers' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Observation towers')",

View file

@ -74,6 +74,25 @@
"key": "charge",
"description": "Layer 'Toilets' shows and asks freeform values for key 'charge' (in the MapComplete.osm.be theme 'Open Toilet Map')"
},
{
"key": "payment:cash",
"description": "Layer 'Toilets' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
"value": "yes"
},
{
"key": "payment:cards",
"description": "Layer 'Toilets' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
"value": "yes"
},
{
"key": "opening_hours",
"description": "Layer 'Toilets' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Open Toilet Map')"
},
{
"key": "opening_hours",
"description": "Layer 'Toilets' shows opening_hours=24/7 with a fixed text, namely 'Opened 24/7' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
"value": "24/7"
},
{
"key": "wheelchair",
"description": "Layer 'Toilets' shows wheelchair=yes with a fixed text, namely 'There is a dedicated toilet for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
@ -150,7 +169,7 @@
},
{
"key": "toilets:paper_supplied",
"description": "Layer 'Toilets' shows toilets:paper_supplied=yes with a fixed text, namely 'Toilet paper is equipped with toilet paper' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
"description": "Layer 'Toilets' shows toilets:paper_supplied=yes with a fixed text, namely 'This toilet is equipped with toilet paper' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Toilet Map')",
"value": "yes"
},
{

View file

@ -1,7 +1,7 @@
from datetime import datetime
from matplotlib import pyplot
import json
import sys
from datetime import datetime
from matplotlib import pyplot
def pyplot_init():
@ -14,20 +14,22 @@ def pyplot_init():
def genKeys(data, type):
keys = map(lambda kv: kv["key"], data)
if type == "date":
keys = map(lambda key : datetime.strptime(key, "%Y-%m-%dT%H:%M:%S.000Z"), keys)
keys = map(lambda key: datetime.strptime(key, "%Y-%m-%dT%H:%M:%S.000Z"), keys)
return list(keys)
def createPie(options):
data = options["plot"]["count"]
keys = genKeys(data, options["interpetKeysAs"])
values = list(map(lambda kv: kv["value"], data))
total = sum(map(lambda kv : kv["value"], data))
total = sum(map(lambda kv: kv["value"], data))
first_pct = data[0]["value"] / total
pyplot_init()
pyplot.pie(values, labels=keys, startangle=(90 - 360 * first_pct / 2))
def createBar(options):
data = options["plot"]["count"]
keys = genKeys(data, options["interpetKeysAs"])
@ -37,26 +39,25 @@ def createBar(options):
pyplot.legend()
pyplot_init()
title = sys.argv[1]
pyplot.title = title
names = []
while(True):
while (True):
line = sys.stdin.readline()
if line == "" or line == "\n":
if(len(names) > 1):
pyplot.legend(loc="upper left", ncol=3)
pyplot.savefig(title+".png", dpi=400, facecolor='w', edgecolor='w',
if (len(names) > 1):
pyplot.legend(loc="upper left", ncol=3)
pyplot.savefig(title + ".png", dpi=400, facecolor='w', edgecolor='w',
bbox_inches='tight')
break
options = json.loads(line)
print("Creating "+options["plot"]["type"]+" '"+options["name"]+"'")
print("Creating " + options["plot"]["type"] + " '" + options["name"] + "'")
names.append(options["name"])
if(options["plot"]["type"] == "pie"):
if (options["plot"]["type"] == "pie"):
createPie(options)
elif(options["plot"]["type"] == "bar"):
elif (options["plot"]["type"] == "bar"):
createBar(options)
else:
print("Unkown type: "+options.type)
print("Unkown type: " + options.type)

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 KiB

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 737 KiB

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 KiB

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -5,8 +5,10 @@ import Loc from "../../Models/Loc";
export interface AvailableBaseLayersObj {
readonly osmCarto: BaseLayer;
layerOverview: BaseLayer[];
AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]>
SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> ;
SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer>;
}
@ -31,7 +33,7 @@ export default class AvailableBaseLayers {
}
public static implement(backend: AvailableBaseLayersObj){
public static implement(backend: AvailableBaseLayersObj) {
AvailableBaseLayers.layerOverview = backend.layerOverview
AvailableBaseLayers.osmCarto = backend.osmCarto
AvailableBaseLayers.implementation = backend

View file

@ -3,13 +3,13 @@ import {UIEventSource} from "../UIEventSource";
import Loc from "../../Models/Loc";
import {GeoOperations} from "../GeoOperations";
import * as editorlayerindex from "../../assets/editor-layer-index.json";
import * as L from "leaflet";
import {TileLayer} from "leaflet";
import * as X from "leaflet-providers";
import * as L from "leaflet";
import {Utils} from "../../Utils";
import {AvailableBaseLayersObj} from "./AvailableBaseLayers";
export default class AvailableBaseLayersImplementation implements AvailableBaseLayersObj{
export default class AvailableBaseLayersImplementation implements AvailableBaseLayersObj {
public readonly osmCarto: BaseLayer =
{
@ -28,102 +28,6 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
public layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex());
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
const source = location.map(
(currentLocation) => {
if (currentLocation === undefined) {
return this.layerOverview;
}
const currentLayers = source?.data; // A bit unorthodox - I know
const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
if (currentLayers === undefined) {
return newLayers;
}
if (newLayers.length !== currentLayers.length) {
return newLayers;
}
for (let i = 0; i < newLayers.length; i++) {
if (newLayers[i].name !== currentLayers[i].name) {
return newLayers;
}
}
return currentLayers;
});
return source;
}
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
return this.AvailableLayersAt(location).map(available => {
// First float all 'best layers' to the top
available.sort((a, b) => {
if (a.isBest && b.isBest) {
return 0;
}
if (!a.isBest) {
return 1
}
return -1;
}
)
if (preferedCategory.data === undefined) {
return available[0]
}
let prefered: string []
if (typeof preferedCategory.data === "string") {
prefered = [preferedCategory.data]
} else {
prefered = preferedCategory.data;
}
prefered.reverse();
for (const category of prefered) {
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
available.sort((a, b) => {
if (a.category === category && b.category === category) {
return 0;
}
if (a.category !== category) {
return 1
}
return -1;
}
)
}
return available[0]
})
}
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [this.osmCarto]
const globalLayers = [];
for (const layerOverviewItem of this.layerOverview) {
const layer = layerOverviewItem;
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
globalLayers.push(layer);
continue;
}
if (lon === undefined || lat === undefined) {
continue;
}
if (GeoOperations.inside([lon, lat], layer.feature)) {
availableLayers.push(layer);
}
}
return availableLayers.concat(globalLayers);
}
private static LoadRasterIndex(): BaseLayer[] {
const layers: BaseLayer[] = []
// @ts-ignore
@ -289,4 +193,100 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
subdomains: domains
});
}
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
const source = location.map(
(currentLocation) => {
if (currentLocation === undefined) {
return this.layerOverview;
}
const currentLayers = source?.data; // A bit unorthodox - I know
const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
if (currentLayers === undefined) {
return newLayers;
}
if (newLayers.length !== currentLayers.length) {
return newLayers;
}
for (let i = 0; i < newLayers.length; i++) {
if (newLayers[i].name !== currentLayers[i].name) {
return newLayers;
}
}
return currentLayers;
});
return source;
}
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
return this.AvailableLayersAt(location).map(available => {
// First float all 'best layers' to the top
available.sort((a, b) => {
if (a.isBest && b.isBest) {
return 0;
}
if (!a.isBest) {
return 1
}
return -1;
}
)
if (preferedCategory.data === undefined) {
return available[0]
}
let prefered: string []
if (typeof preferedCategory.data === "string") {
prefered = [preferedCategory.data]
} else {
prefered = preferedCategory.data;
}
prefered.reverse();
for (const category of prefered) {
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
available.sort((a, b) => {
if (a.category === category && b.category === category) {
return 0;
}
if (a.category !== category) {
return 1
}
return -1;
}
)
}
return available[0]
})
}
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [this.osmCarto]
const globalLayers = [];
for (const layerOverviewItem of this.layerOverview) {
const layer = layerOverviewItem;
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
globalLayers.push(layer);
continue;
}
if (lon === undefined || lat === undefined) {
continue;
}
if (GeoOperations.inside([lon, lat], layer.feature)) {
availableLayers.push(layer);
}
}
return availableLayers.concat(globalLayers);
}
}

View file

@ -14,7 +14,7 @@ export default class BackgroundLayerResetter {
availableLayers: UIEventSource<BaseLayer[]>,
defaultLayerId: string = undefined) {
if(Utils.runningFromConsole){
if (Utils.runningFromConsole) {
return
}

View file

@ -5,11 +5,22 @@ 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 interface GeoLocationPointProperties {
id: "gps",
"user:location": "yes",
"date": string,
"latitude": number
"longitude":number,
"speed": number,
"accuracy": number
"heading": number
"altitude":number
}
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 +70,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,19 +193,24 @@ 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: {
"user:location":"yes",
"accuracy":location.accuracy,
"speed":location.speed,
properties: <GeoLocationPointProperties>{
id: "gps",
"user:location": "yes",
"date": new Date().toISOString(),
"latitude": location.latitude,
"longitude": location.longitude,
"speed": location.speed,
"accuracy": location.accuracy,
"heading": location.heading,
"altitude": location.altitude
},
geometry:{
type:"Point",
geometry: {
type: "Point",
coordinates: [location.longitude, location.latitude],
}
}

View file

@ -115,7 +115,6 @@ export default class OverpassFeatureSource implements FeatureSource {
let lastUsed = 0;
const layersToDownload = []
for (const layer of this.state.layoutToUse.layers) {
@ -137,7 +136,7 @@ export default class OverpassFeatureSource implements FeatureSource {
const self = this;
const overpassUrls = self.state.overpassUrl.data
let bounds : BBox
let bounds: BBox
do {
try {
@ -182,7 +181,7 @@ export default class OverpassFeatureSource implements FeatureSource {
try {
if(data === undefined){
if (data === undefined) {
return undefined
}
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined));

View file

@ -31,7 +31,7 @@ export default class PendingChangesUploader {
}
});
if(Utils.runningFromConsole){
if (Utils.runningFromConsole) {
return;
}

View file

@ -10,7 +10,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
* Makes sure the hash shows the selected element and vice-versa.
*/
export default class SelectedFeatureHandler {
private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filters","", undefined])
private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filters", "location_track", "", undefined])
private readonly hash: UIEventSource<string>;
private readonly state: {
selectedElement: UIEventSource<any>,

View file

@ -81,5 +81,4 @@ export default class StrayClickHandler {
}
}

View file

@ -8,7 +8,7 @@ import {ElementStorage} from "../ElementStorage";
import {Utils} from "../../Utils";
export default class TitleHandler {
constructor(state : {
constructor(state: {
selectedElement: UIEventSource<any>,
layoutToUse: LayoutConfig,
allElements: ElementStorage
@ -28,7 +28,7 @@ export default class TitleHandler {
continue;
}
if (layer.source.osmTags.matchesProperties(tags)) {
const tagsSource = state.allElements.getEventSourceById(tags.id)
const tagsSource = state.allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags)
const title = new TagRenderingAnswer(tagsSource, layer.title)
return new Combine([defaultTitle, " | ", title]).ConstructElement()?.innerText ?? defaultTitle;
}
@ -39,7 +39,7 @@ export default class TitleHandler {
currentTitle.addCallbackAndRunD(title => {
if(Utils.runningFromConsole){
if (Utils.runningFromConsole) {
return
}
document.title = title

View file

@ -4,11 +4,11 @@ import {GeoOperations} from "./GeoOperations";
export class BBox {
static global: BBox = new BBox([[-180, -90], [180, 90]]);
readonly maxLat: number;
readonly maxLon: number;
readonly minLat: number;
readonly minLon: number;
static global: BBox = new BBox([[-180, -90], [180, 90]]);
constructor(coordinates) {
this.maxLat = -90;
@ -45,6 +45,17 @@ export class BBox {
return feature.bbox;
}
static fromTile(z: number, x: number, y: number): BBox {
return new BBox(Tiles.tile_bounds_lon_lat(z, x, y))
}
static fromTileIndex(i: number): BBox {
if (i === 0) {
return BBox.global
}
return BBox.fromTile(...Tiles.tile_from_index(i))
}
/**
* Constructs a tilerange which fully contains this bbox (thus might be a bit larger)
* @param zoomlevel
@ -83,24 +94,6 @@ export class BBox {
return true;
}
private check() {
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
console.log(this);
throw "BBOX has NAN";
}
}
static fromTile(z: number, x: number, y: number): BBox {
return new BBox(Tiles.tile_bounds_lon_lat(z, x, y))
}
static fromTileIndex(i: number): BBox {
if (i === 0) {
return BBox.global
}
return BBox.fromTile(...Tiles.tile_from_index(i))
}
getEast() {
return this.maxLon
}
@ -117,9 +110,9 @@ export class BBox {
return this.minLat
}
contains(lonLat: [number, number]){
contains(lonLat: [number, number]) {
return this.minLat <= lonLat[1] && lonLat[1] <= this.maxLat
&& this.minLon<= lonLat[0] && lonLat[0] <= this.maxLon
&& this.minLon <= lonLat[0] && lonLat[0] <= this.maxLon
}
pad(factor: number, maxIncrease = 2): BBox {
@ -179,4 +172,11 @@ export class BBox {
}
private check() {
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
console.log(this);
throw "BBOX has NAN";
}
}
}

View file

@ -8,6 +8,7 @@ export default class ContributorCount {
public readonly Contributors: UIEventSource<Map<string, number>> = new UIEventSource<Map<string, number>>(new Map<string, number>());
private readonly state: { featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc> };
private lastUpdate: Date = undefined;
constructor(state: { featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc> }) {
this.state = state;
@ -16,15 +17,13 @@ export default class ContributorCount {
self.update(bbox)
})
state.featurePipeline.runningQuery.addCallbackAndRun(
_ => self.update(state.currentBounds.data)
_ => self.update(state.currentBounds.data)
)
}
private lastUpdate: Date = undefined;
private update(bbox: BBox) {
if(bbox === undefined){
if (bbox === undefined) {
return;
}
const now = new Date();

View file

@ -65,39 +65,6 @@ export default class DetermineLayout {
return [layoutToUse, undefined]
}
private static async LoadRemoteTheme(link: string): Promise<LayoutConfig | null> {
console.log("Downloading map theme from ", link);
new FixedUiElement(`Downloading the theme from the <a href="${link}">link</a>...`)
.AttachTo("centermessage");
try {
const parsed = await Utils.downloadJson(link)
console.log("Got ", parsed)
LegacyJsonConvert.fixThemeConfig(parsed)
try {
parsed.id = link;
return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed));
} catch (e) {
console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`<a href="${link}">${link}</a> is invalid:`,
new FixedUiElement(e)
)
return null;
}
} catch (e) {
console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`<a href="${link}">${link}</a> is invalid - probably not found or invalid JSON:`,
new FixedUiElement(e)
)
return null;
}
}
public static LoadLayoutFromHash(
userLayoutParam: UIEventSource<string>
): [LayoutConfig, string] | null {
@ -166,4 +133,37 @@ export default class DetermineLayout {
.AttachTo("centermessage");
}
private static async LoadRemoteTheme(link: string): Promise<LayoutConfig | null> {
console.log("Downloading map theme from ", link);
new FixedUiElement(`Downloading the theme from the <a href="${link}">link</a>...`)
.AttachTo("centermessage");
try {
const parsed = await Utils.downloadJson(link)
console.log("Got ", parsed)
LegacyJsonConvert.fixThemeConfig(parsed)
try {
parsed.id = link;
return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed));
} catch (e) {
console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`<a href="${link}">${link}</a> is invalid:`,
new FixedUiElement(e)
)
return null;
}
} catch (e) {
console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`<a href="${link}">${link}</a> is invalid - probably not found or invalid JSON:`,
new FixedUiElement(e)
)
return null;
}
}
}

View file

@ -39,10 +39,10 @@ export class ElementStorage {
}
getEventSourceById(elementId): UIEventSource<any> {
if(elementId === undefined){
if (elementId === undefined) {
return undefined;
}
return this._elements.get(elementId);
return this._elements.get(elementId);
}
has(id) {

View file

@ -64,7 +64,7 @@ export class ExtraFunction {
},
(params, feat) => {
return (...layerIds: string[]) => {
const result : {feat:any, overlap: number}[]= []
const result: { feat: any, overlap: number }[] = []
const bbox = BBox.get(feat)
@ -90,7 +90,7 @@ export class ExtraFunction {
private static readonly DistanceToFunc = new ExtraFunction(
{
name: "distanceTo",
doc: "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",
doc: "Calculates the distance between the feature and a specified point in meter. The input should either be a pair of coordinates, a geojson feature or the ID of an object",
args: ["feature OR featureID OR longitude", "undefined OR latitude"]
},
(featuresPerLayer, feature) => {
@ -181,7 +181,7 @@ export class ExtraFunction {
}
try {
const parsed = JSON.parse(value)
if(parsed === null){
if (parsed === null) {
return undefined;
}
return parsed;

View file

@ -39,7 +39,7 @@ export default class SaveTileToLocalStorageActor {
}
}
public static poison(layers: string[], lon: number, lat: number) {
public static poison(layers: string[], lon: number, lat: number) {
for (let z = 0; z < 25; z++) {
const {x, y} = Tiles.embedded_tile(lat, lon, z)

View file

@ -5,11 +5,9 @@ import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from
import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
import {UIEventSource} from "../UIEventSource";
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
import FilteredLayer from "../../Models/FilteredLayer";
import MetaTagging from "../MetaTagging";
import RememberingSource from "./Sources/RememberingSource";
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
import {Changes} from "../Osm/Changes";
import GeoJsonSource from "./Sources/GeoJsonSource";
import Loc from "../../Models/Loc";
import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor";
@ -22,11 +20,10 @@ import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChan
import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator";
import {BBox} from "../BBox";
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
import {OsmConnection} from "../Osm/OsmConnection";
import {Tiles} from "../../Models/TileRange";
import TileFreshnessCalculator from "./TileFreshnessCalculator";
import {ElementStorage} from "../ElementStorage";
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
import MapState from "../State/MapState";
/**
@ -51,19 +48,7 @@ export default class FeaturePipeline {
public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
private readonly overpassUpdater: OverpassFeatureSource
private state: {
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
readonly locationControl: UIEventSource<Loc>,
readonly selectedElement: UIEventSource<any>,
readonly changes: Changes,
readonly layoutToUse: LayoutConfig,
readonly leafletMap: any,
readonly overpassUrl: UIEventSource<string[]>;
readonly overpassTimeout: UIEventSource<number>;
readonly overpassMaxZoom: UIEventSource<number>;
readonly osmConnection: OsmConnection
readonly currentBounds: UIEventSource<BBox>
};
private state: MapState;
private readonly relationTracker: RelationsTracker
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
@ -74,21 +59,7 @@ export default class FeaturePipeline {
constructor(
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
state: {
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
readonly locationControl: UIEventSource<Loc>,
readonly selectedElement: UIEventSource<any>,
readonly changes: Changes,
readonly layoutToUse: LayoutConfig,
readonly leafletMap: any,
readonly overpassUrl: UIEventSource<string[]>;
readonly overpassTimeout: UIEventSource<number>;
readonly overpassMaxZoom: UIEventSource<number>;
readonly osmConnection: OsmConnection
readonly currentBounds: UIEventSource<BBox>,
readonly osmApiTileSize: UIEventSource<number>,
readonly allElements: ElementStorage
}) {
state: MapState) {
this.state = state;
const self = this
@ -125,17 +96,25 @@ export default class FeaturePipeline {
const perLayerHierarchy = new Map<string, TileHierarchyMerger>()
this.perLayerHierarchy = perLayerHierarchy
const patchedHandleFeatureSource = function (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) {
function patchedHandleFeatureSource (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) {
// This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
const srcFiltered =
new FilteringFeatureSource(state, src.tileIndex,
new ChangeGeometryApplicator(src, state.changes)
new ChangeGeometryApplicator(src, state.changes)
)
handleFeatureSource(srcFiltered)
self.somethingLoaded.setData(true)
// We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
};
}
function handlePriviligedFeatureSource(src: FeatureSourceForLayer & Tiled){
// Passthrough to passed function, except that it registers as well
handleFeatureSource(src)
src.features.addCallbackAndRunD(fs => {
fs.forEach(ff => state.allElements.addOrGetElement(ff.feature))
})
}
for (const filteredLayer of state.filteredLayers.data) {
@ -147,11 +126,31 @@ export default class FeaturePipeline {
this.freshnesses.set(id, new TileFreshnessCalculator())
if(id === "type_node"){
if (id === "type_node") {
// Handles by the 'FullNodeDatabaseSource'
continue;
}
if (id === "gps_location") {
handlePriviligedFeatureSource(state.currentUserLocation)
continue
}
if (id === "gps_location_history") {
handlePriviligedFeatureSource(state.historicalUserLocations)
continue
}
if (id === "gps_track") {
handlePriviligedFeatureSource(state.historicalUserLocationsTrack)
continue
}
if (id === "home_location") {
handlePriviligedFeatureSource(state.homeLocation)
continue
}
if (source.geojsonSource === undefined) {
// This is an OSM layer
// We load the cached values and register them
@ -227,14 +226,14 @@ export default class FeaturePipeline {
})
})
if(state.layoutToUse.trackAllNodes){
const fullNodeDb = new FullNodeDatabaseSource(
state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0],
tile => {
new RegisteringAllFromFeatureSourceActor(tile)
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
})
if (state.layoutToUse.trackAllNodes) {
const fullNodeDb = new FullNodeDatabaseSource(
state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0],
tile => {
new RegisteringAllFromFeatureSourceActor(tile)
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
})
osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => fullNodeDb.handleOsmJson(osmJson, tileId))
}
@ -299,6 +298,34 @@ export default class FeaturePipeline {
}
public GetAllFeaturesWithin(bbox: BBox): any[][] {
const self = this
const tiles = []
Array.from(this.perLayerHierarchy.keys())
.forEach(key => tiles.push(...self.GetFeaturesWithin(key, bbox)))
return tiles;
}
public GetFeaturesWithin(layerId: string, bbox: BBox): any[][] {
if (layerId === "*") {
return this.GetAllFeaturesWithin(bbox)
}
const requestedHierarchy = this.perLayerHierarchy.get(layerId)
if (requestedHierarchy === undefined) {
console.warn("Layer ", layerId, "is not defined. Try one of ", Array.from(this.perLayerHierarchy.keys()))
return undefined;
}
return TileHierarchyTools.getTiles(requestedHierarchy, bbox)
.filter(featureSource => featureSource.features?.data !== undefined)
.map(featureSource => featureSource.features.data.map(fs => fs.feature))
}
public GetTilesPerLayerWithin(bbox: BBox, handleTile: (tile: FeatureSourceForLayer & Tiled) => void) {
Array.from(this.perLayerHierarchy.values()).forEach(hierarchy => {
TileHierarchyTools.getTiles(hierarchy, bbox).forEach(handleTile)
})
}
private freshnessForVisibleLayers(z: number, x: number, y: number): Date {
let oldestDate = undefined;
for (const flayer of this.state.filteredLayers.data) {
@ -438,32 +465,4 @@ export default class FeaturePipeline {
}
public GetAllFeaturesWithin(bbox: BBox): any[][] {
const self = this
const tiles = []
Array.from(this.perLayerHierarchy.keys())
.forEach(key => tiles.push(...self.GetFeaturesWithin(key, bbox)))
return tiles;
}
public GetFeaturesWithin(layerId: string, bbox: BBox): any[][] {
if (layerId === "*") {
return this.GetAllFeaturesWithin(bbox)
}
const requestedHierarchy = this.perLayerHierarchy.get(layerId)
if (requestedHierarchy === undefined) {
console.warn("Layer ", layerId, "is not defined. Try one of ", Array.from(this.perLayerHierarchy.keys()))
return undefined;
}
return TileHierarchyTools.getTiles(requestedHierarchy, bbox)
.filter(featureSource => featureSource.features?.data !== undefined)
.map(featureSource => featureSource.features.data.map(fs => fs.feature))
}
public GetTilesPerLayerWithin(bbox: BBox, handleTile: (tile: FeatureSourceForLayer & Tiled) => void) {
Array.from(this.perLayerHierarchy.values()).forEach(hierarchy => {
TileHierarchyTools.getTiles(hierarchy, bbox).forEach(handleTile)
})
}
}

View file

@ -1,5 +1,4 @@
import {UIEventSource} from "../UIEventSource";
import {Utils} from "../../Utils";
import FilteredLayer from "../../Models/FilteredLayer";
import {BBox} from "../BBox";
@ -19,7 +18,7 @@ export interface Tiled {
/**
* A feature source which only contains features for the defined layer
*/
export interface FeatureSourceForLayer extends FeatureSource{
export interface FeatureSourceForLayer extends FeatureSource {
readonly layer: FilteredLayer
}

View file

@ -14,15 +14,15 @@ export default class PerLayerFeatureSourceSplitter {
constructor(layers: UIEventSource<FilteredLayer[]>,
handleLayerData: (source: FeatureSourceForLayer & Tiled) => void,
upstream: FeatureSource,
options?:{
tileIndex?: number,
handleLeftovers?: (featuresWithoutLayer: any[]) => void
options?: {
tileIndex?: number,
handleLeftovers?: (featuresWithoutLayer: any[]) => void
}) {
const knownLayers = new Map<string, FeatureSourceForLayer & Tiled>()
function update() {
const features = upstream.features.data;
const features = upstream.features?.data;
if (features === undefined) {
return;
}
@ -35,6 +35,7 @@ export default class PerLayerFeatureSourceSplitter {
const featuresPerLayer = new Map<string, { feature, freshness } []>();
const noLayerFound = []
function addTo(layer: FilteredLayer, feature: { feature, freshness }) {
const id = layer.layerDef.id
const list = featuresPerLayer.get(id)
@ -82,7 +83,7 @@ export default class PerLayerFeatureSourceSplitter {
}
// AT last, the leftovers are handled
if(options?.handleLeftovers !== undefined && noLayerFound.length > 0){
if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) {
options.handleLeftovers(noLayerFound)
}
}

View file

@ -11,9 +11,9 @@ import {ChangeDescription, ChangeDescriptionTools} from "../../Osm/Actions/Chang
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name: string;
public readonly layer: FilteredLayer
private readonly source: IndexedFeatureSource;
private readonly changes: Changes;
public readonly layer: FilteredLayer
constructor(source: (IndexedFeatureSource & FeatureSourceForLayer), changes: Changes) {
this.source = source;
@ -52,9 +52,9 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
const changesPerId = new Map<string, ChangeDescription[]>()
for (const ch of changesToApply) {
const key = ch.type + "/" + ch.id
if(changesPerId.has(key)){
if (changesPerId.has(key)) {
changesPerId.get(key).push(ch)
}else{
} else {
changesPerId.set(key, [ch])
}
}

View file

@ -5,7 +5,6 @@
import {UIEventSource} from "../../UIEventSource";
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {Utils} from "../../../Utils";
import {Tiles} from "../../../Models/TileRange";
import {BBox} from "../../BBox";
@ -14,17 +13,17 @@ export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name;
public readonly layer: FilteredLayer
private readonly _sources: UIEventSource<FeatureSource[]>;
public readonly tileIndex: number;
public readonly bbox: BBox;
public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(new Set())
private readonly _sources: UIEventSource<FeatureSource[]>;
constructor(layer: FilteredLayer, tileIndex: number, bbox: BBox, sources: UIEventSource<FeatureSource[]>) {
this.tileIndex = tileIndex;
this.bbox = bbox;
this._sources = sources;
this.layer = layer;
this.name = "FeatureSourceMerger("+layer.layerDef.id+", "+Tiles.tile_from_index(tileIndex).join(",")+")"
this.name = "FeatureSourceMerger(" + layer.layerDef.id + ", " + Tiles.tile_from_index(tileIndex).join(",") + ")"
const self = this;
const handledSources = new Set<FeatureSource>();

View file

@ -18,6 +18,8 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource<any>,
allElements: ElementStorage
};
private readonly _alreadyRegistered = new Set<UIEventSource<any>>();
private readonly _is_dirty = new UIEventSource(false)
constructor(
state: {
@ -55,24 +57,6 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this.update();
}
private readonly _alreadyRegistered = new Set<UIEventSource<any>>();
private readonly _is_dirty = new UIEventSource(false)
private registerCallback(feature: any, layer: LayerConfig) {
const src = this.state.allElements.addOrGetElement(feature)
if (this._alreadyRegistered.has(src)) {
return
}
this._alreadyRegistered.add(src)
if (layer.isShown !== undefined) {
const self = this;
src.map(tags => layer.isShown?.GetRenderValue(tags, "yes").txt).addCallbackAndRunD(isShown => {
self._is_dirty.setData(true)
})
}
}
public update() {
const self = this;
const layer = this.upstream.layer;
@ -116,4 +100,19 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this._is_dirty.setData(false)
}
private registerCallback(feature: any, layer: LayerConfig) {
const src = this.state.allElements.addOrGetElement(feature)
if (this._alreadyRegistered.has(src)) {
return
}
this._alreadyRegistered.add(src)
if (layer.isShown !== undefined) {
const self = this;
src.map(tags => layer.isShown?.GetRenderValue(tags, "yes").txt).addCallbackAndRunD(isShown => {
self._is_dirty.setData(true)
})
}
}
}

View file

@ -15,12 +15,10 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name;
public readonly isOsmCache: boolean
private readonly seenids: Set<string> = new Set<string>()
public readonly layer: FilteredLayer;
public readonly tileIndex
public readonly bbox;
private readonly seenids: Set<string> = new Set<string>()
/**
* Only used if the actual source is a tiled geojson.
* A big feature might be contained in multiple tiles.
@ -32,7 +30,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
public constructor(flayer: FilteredLayer,
zxy?: [number, number, number],
options?: {
featureIdBlacklist?: UIEventSource<Set<string>>
featureIdBlacklist?: UIEventSource<Set<string>>
}) {
if (flayer.layerDef.source.geojsonZoomLevel !== undefined && zxy === undefined) {
@ -45,18 +43,18 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
if (zxy !== undefined) {
const [z, x, y] = zxy;
let tile_bbox = BBox.fromTile(z, x, y)
let bounds : { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox
if(this.layer.layerDef.source.mercatorCrs){
let bounds: { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox
if (this.layer.layerDef.source.mercatorCrs) {
bounds = tile_bbox.toMercator()
}
url = url
.replace('{z}', "" + z)
.replace('{x}', "" + x)
.replace('{y}', "" + y)
.replace('{y_min}',""+bounds.minLat)
.replace('{y_max}',""+bounds.maxLat)
.replace('{x_min}',""+bounds.minLon)
.replace('{x_max}',""+bounds.maxLon)
.replace('{y_min}', "" + bounds.minLat)
.replace('{y_max}', "" + bounds.maxLat)
.replace('{x_min}', "" + bounds.minLon)
.replace('{x_max}', "" + bounds.maxLon)
this.tileIndex = Tiles.tile_index(z, x, y)
this.bbox = BBox.fromTile(z, x, y)
@ -78,11 +76,11 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
const self = this;
Utils.downloadJson(url)
.then(json => {
if(json.features === undefined || json.features === null){
if (json.features === undefined || json.features === null) {
return;
}
if(self.layer.layerDef.source.mercatorCrs){
if (self.layer.layerDef.source.mercatorCrs) {
json = GeoOperations.GeoJsonToWGS84(json)
}
@ -110,7 +108,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
}
self.seenids.add(props.id)
if(self.featureIdBlacklist?.data?.has(props.id)){
if (self.featureIdBlacklist?.data?.has(props.id)) {
continue;
}
@ -122,7 +120,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
newFeatures.push({feature: feature, freshness: freshness})
}
if ( newFeatures.length == 0) {
if (newFeatures.length == 0) {
return;
}

View file

@ -53,7 +53,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
for (const kv of change.tags) {
tags[kv.k] = kv.v
}
tags["id"] = change.type+"/"+change.id
tags["id"] = change.type + "/" + change.id
tags["_backend"] = State.state.osmConnection._oauth_config.url

View file

@ -6,17 +6,17 @@ import FeatureSource, {Tiled} from "../FeatureSource";
import {UIEventSource} from "../../UIEventSource";
import {BBox} from "../../BBox";
export default class RememberingSource implements FeatureSource , Tiled{
export default class RememberingSource implements FeatureSource, Tiled {
public readonly features: UIEventSource<{ feature: any, freshness: Date }[]>;
public readonly name;
public readonly tileIndex : number
public readonly bbox : BBox
public readonly tileIndex: number
public readonly bbox: BBox
constructor(source: FeatureSource & Tiled) {
const self = this;
this.name = "RememberingSource of " + source.name;
this.tileIndex= source.tileIndex
this.tileIndex = source.tileIndex
this.bbox = source.bbox;
const empty = [];

View file

@ -7,7 +7,6 @@ import FeatureSource from "../FeatureSource";
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
export default class RenderingMultiPlexerFeatureSource {
public readonly features: UIEventSource<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>;
@ -29,10 +28,10 @@ export default class RenderingMultiPlexerFeatureSource {
const lineRenderObjects = layer.lineRendering
const withIndex: (any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[] = [];
const withIndex: (any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined, multiLineStringIndex: number | undefined })[] = [];
function addAsPoint(feat, rendering, coordinate) {
function addAsPoint(feat, rendering, coordinate) {
const patched = {
...feat,
pointRenderingIndex: rendering.index
@ -55,12 +54,14 @@ export default class RenderingMultiPlexerFeatureSource {
})
}
} else {
// This is a a line
// This is a a line: add the centroids
for (const rendering of centroidRenderings) {
addAsPoint(feat, rendering, GeoOperations.centerpointCoordinates(feat))
}
if (feat.geometry.type === "LineString") {
// Add start- and endpoints
const coordinates = feat.geometry.coordinates
for (const rendering of startRenderings) {
addAsPoint(feat, rendering, coordinates[0])
@ -69,33 +70,45 @@ export default class RenderingMultiPlexerFeatureSource {
const coordinate = coordinates[coordinates.length - 1]
addAsPoint(feat, rendering, coordinate)
}
}
if (feat.geometry.type === "MultiLineString") {
const lineList = feat.geometry.coordinates
for (const coordinates of lineList) {
for (const rendering of startRenderings) {
const coordinate = coordinates[0]
addAsPoint(feat, rendering, coordinate)
}
for (const rendering of endRenderings) {
const coordinate = coordinates[coordinates.length - 1]
addAsPoint(feat, rendering, coordinate)
if (feat.geometry.type === "MultiLineString") {
// Multilinestrings get a special treatment: we split them into their subparts before rendering
const lineList: [number, number][][] = feat.geometry.coordinates
for (let i1 = 0; i1 < lineList.length; i1++) {
const coordinates = lineList[i1];
for (let i = 0; i < lineRenderObjects.length; i++) {
const orig = {
...feat,
lineRenderingIndex: i,
multiLineStringIndex: i1
}
orig.geometry.coordinates = coordinates
orig.geometry.type = "LineString"
withIndex.push(orig)
}
}
}else{
// AT last, add it 'as is' to what we should render
for (let i = 0; i < lineRenderObjects.length; i++) {
withIndex.push({
...feat,
lineRenderingIndex: i
})
}
}
for (let i = 0; i < lineRenderObjects.length; i++) {
withIndex.push({
...feat,
lineRenderingIndex: i
})
}
}
}
return withIndex;
}
);

View file

@ -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 }[]>([]);
}
}

View file

@ -13,35 +13,35 @@ export default class TileFreshnessCalculator {
* @param tileId
* @param freshness
*/
public addTileLoad(tileId: number, freshness: Date){
public addTileLoad(tileId: number, freshness: Date) {
const existingFreshness = this.freshnessFor(...Tiles.tile_from_index(tileId))
if(existingFreshness >= freshness){
if (existingFreshness >= freshness) {
return;
}
this.freshnesses.set(tileId, freshness)
// Do we have freshness for the neighbouring tiles? If so, we can mark the tile above as loaded too!
let [z, x, y] = Tiles.tile_from_index(tileId)
if(z === 0){
if (z === 0) {
return;
}
x = x - (x % 2) // Make the tiles always even
y = y - (y % 2)
const ul = this.freshnessFor(z, x, y)?.getTime()
if(ul === undefined){
if (ul === undefined) {
return
}
const ur = this.freshnessFor(z, x + 1, y)?.getTime()
if(ur === undefined){
if (ur === undefined) {
return
}
const ll = this.freshnessFor(z, x, y + 1)?.getTime()
if(ll === undefined){
if (ll === undefined) {
return
}
const lr = this.freshnessFor(z, x + 1, y + 1)?.getTime()
if(lr === undefined){
if (lr === undefined) {
return
}
@ -50,21 +50,21 @@ export default class TileFreshnessCalculator {
date.setTime(leastFresh)
this.addTileLoad(
Tiles.tile_index(z - 1, Math.floor(x / 2), Math.floor(y / 2)),
date
date
)
}
public freshnessFor(z: number, x: number, y:number): Date {
if(z < 0){
public freshnessFor(z: number, x: number, y: number): Date {
if (z < 0) {
return undefined
}
const tileId = Tiles.tile_index(z, x, y)
if(this.freshnesses.has(tileId)) {
if (this.freshnesses.has(tileId)) {
return this.freshnesses.get(tileId)
}
// recurse up
return this.freshnessFor(z - 1, Math.floor(x /2), Math.floor(y / 2))
return this.freshnessFor(z - 1, Math.floor(x / 2), Math.floor(y / 2))
}

View file

@ -9,9 +9,8 @@ import {Tiles} from "../../../Models/TileRange";
* A tiled source which dynamically loads the required tiles at a fixed zoom level
*/
export default class DynamicTileSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
private readonly _loadedTiles = new Set<number>();
public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled>;
private readonly _loadedTiles = new Set<number>();
constructor(
layer: FilteredLayer,
@ -24,7 +23,7 @@ export default class DynamicTileSource implements TileHierarchy<FeatureSourceFor
) {
const self = this;
this.loadedTiles = new Map<number,FeatureSourceForLayer & Tiled>()
this.loadedTiles = new Map<number, FeatureSourceForLayer & Tiled>()
const neededTiles = state.locationControl.map(
location => {
if (!layer.isDisplayed.data) {
@ -54,14 +53,14 @@ export default class DynamicTileSource implements TileHierarchy<FeatureSourceFor
, [layer.isDisplayed, state.leafletMap]).stabilized(250);
neededTiles.addCallbackAndRunD(neededIndexes => {
console.log("Tiled geojson source ",layer.layerDef.id," needs", neededIndexes)
console.log("Tiled geojson source ", layer.layerDef.id, " needs", neededIndexes)
if (neededIndexes === undefined) {
return;
}
for (const neededIndex of neededIndexes) {
self._loadedTiles.add(neededIndex)
const src = constructTile(Tiles.tile_from_index(neededIndex))
if(src !== undefined){
if (src !== undefined) {
self.loadedTiles.set(neededIndex, src)
}
}

View file

@ -13,9 +13,10 @@ import {Or} from "../../Tags/Or";
import {TagsFilter} from "../../Tags/TagsFilter";
export default class OsmFeatureSource {
private readonly _backend: string;
public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly downloadedTiles = new Set<number>()
public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = []
private readonly _backend: string;
private readonly filteredLayers: UIEventSource<FilteredLayer[]>;
private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void;
private isActive: UIEventSource<boolean>;
@ -28,11 +29,8 @@ export default class OsmFeatureSource {
},
markTileVisited?: (tileId: number) => void
};
public readonly downloadedTiles = new Set<number>()
private readonly allowedTags: TagsFilter;
public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = []
constructor(options: {
handleTile: (tile: FeatureSourceForLayer & Tiled) => void;
isActive: UIEventSource<boolean>,
@ -57,7 +55,7 @@ export default class OsmFeatureSource {
neededTiles = neededTiles.filter(tile => !self.downloadedTiles.has(tile))
if(neededTiles.length == 0){
if (neededTiles.length == 0) {
return;
}
@ -73,7 +71,7 @@ export default class OsmFeatureSource {
}
} catch (e) {
console.error(e)
}finally {
} finally {
console.log("Done")
self.isRunning.setData(false)
}

View file

@ -11,17 +11,14 @@ Currently, they are:
When the data enters from Overpass or from the OSM-API, they are first distributed per layer:
OVERPASS | ---PerLayerFeatureSource---> FeatureSourceForLayer[]
OSM |
OSM |
The GeoJSon files (not tiled) are then added to this list
A single FeatureSourcePerLayer is then further handled by splitting it into a tile hierarchy.
In order to keep thins snappy, they are distributed over a tiled database per layer.
## Notes
`cached-featuresbookcases` is the old key used `cahced-features{themeid}` and should be cleaned up

View file

@ -8,9 +8,8 @@ import {BBox} from "../../BBox";
export class TileHierarchyMerger implements TileHierarchy<FeatureSourceForLayer & Tiled> {
public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
private readonly sources: Map<number, UIEventSource<FeatureSource[]>> = new Map<number, UIEventSource<FeatureSource[]>>();
public readonly layer: FilteredLayer;
private readonly sources: Map<number, UIEventSource<FeatureSource[]>> = new Map<number, UIEventSource<FeatureSource[]>>();
private _handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource, index: number) => void;
constructor(layer: FilteredLayer, handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled, index: number) => void) {
@ -24,7 +23,7 @@ export class TileHierarchyMerger implements TileHierarchy<FeatureSourceForLayer
* @param src
* @param index
*/
public registerTile(src: FeatureSource & Tiled) {
public registerTile(src: FeatureSource & Tiled) {
const index = src.tileIndex
if (this.sources.has(index)) {

View file

@ -1,6 +1,5 @@
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
import {UIEventSource} from "../../UIEventSource";
import {Utils} from "../../../Utils";
import FilteredLayer from "../../../Models/FilteredLayer";
import TileHierarchy from "./TileHierarchy";
import {Tiles} from "../../../Models/TileRange";
@ -28,13 +27,13 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource,
public readonly containedIds: UIEventSource<Set<string>>
public readonly bbox: BBox;
public readonly tileIndex: number;
private upper_left: TiledFeatureSource
private upper_right: TiledFeatureSource
private lower_left: TiledFeatureSource
private lower_right: TiledFeatureSource
private readonly maxzoom: number;
private readonly options: TiledFeatureSourceOptions
public readonly tileIndex: number;
private constructor(z: number, x: number, y: number, parent: TiledFeatureSource, options?: TiledFeatureSourceOptions) {
this.z = z;
@ -92,16 +91,16 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource,
return root;
}
private isSplitNeeded(featureCount: number){
if(this.upper_left !== undefined){
private isSplitNeeded(featureCount: number) {
if (this.upper_left !== undefined) {
// This tile has been split previously, so we keep on splitting
return true;
}
if(this.z >= this.maxzoom){
if (this.z >= this.maxzoom) {
// We are not allowed to split any further
return false
}
if(this.options.minZoomLevel !== undefined && this.z < this.options.minZoomLevel){
if (this.options.minZoomLevel !== undefined && this.z < this.options.minZoomLevel) {
// We must have at least this zoom level before we are allowed to start splitting
return true
}
@ -155,7 +154,7 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource,
} else {
overlapsboundary.push(feature)
}
}else if (this.options.minZoomLevel === undefined) {
} else if (this.options.minZoomLevel === undefined) {
if (bbox.isContainedIn(this.upper_left.bbox)) {
ulf.push(feature)
} else if (bbox.isContainedIn(this.upper_right.bbox)) {

View file

@ -13,44 +13,6 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
private readonly handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void;
private readonly undefinedTiles: Set<number>;
public static GetFreshnesses(layerId: string): Map<number, Date> {
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layerId + "-"
const freshnesses = new Map<number, Date>()
for (const key of Object.keys(localStorage)) {
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
continue
}
const index = Number(key.substring(prefix.length, key.length - "-time".length))
const time = Number(localStorage.getItem(key))
const freshness = new Date()
freshness.setTime(time)
freshnesses.set(index, freshness)
}
return freshnesses
}
static cleanCacheForLayer(layer: LayerConfig) {
const now = new Date()
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.id + "-"
console.log("Cleaning tiles of ", prefix, "with max age",layer.maxAgeOfCache)
for (const key of Object.keys(localStorage)) {
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
continue
}
const index = Number(key.substring(prefix.length, key.length - "-time".length))
const time = Number(localStorage.getItem(key))
const timeDiff = (now.getTime() - time) / 1000
if(timeDiff >= layer.maxAgeOfCache){
const k = prefix+index;
localStorage.removeItem(k)
localStorage.removeItem(k+"-format")
localStorage.removeItem(k+"-time")
}
}
}
constructor(layer: FilteredLayer,
handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
state: {
@ -110,6 +72,43 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
}
public static GetFreshnesses(layerId: string): Map<number, Date> {
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layerId + "-"
const freshnesses = new Map<number, Date>()
for (const key of Object.keys(localStorage)) {
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
continue
}
const index = Number(key.substring(prefix.length, key.length - "-time".length))
const time = Number(localStorage.getItem(key))
const freshness = new Date()
freshness.setTime(time)
freshnesses.set(index, freshness)
}
return freshnesses
}
static cleanCacheForLayer(layer: LayerConfig) {
const now = new Date()
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.id + "-"
console.log("Cleaning tiles of ", prefix, "with max age", layer.maxAgeOfCache)
for (const key of Object.keys(localStorage)) {
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
continue
}
const index = Number(key.substring(prefix.length, key.length - "-time".length))
const time = Number(localStorage.getItem(key))
const timeDiff = (now.getTime() - time) / 1000
if (timeDiff >= layer.maxAgeOfCache) {
const k = prefix + index;
localStorage.removeItem(k)
localStorage.removeItem(k + "-format")
localStorage.removeItem(k + "-time")
}
}
}
private loadTile(neededIndex: number) {
try {
const key = SaveTileToLocalStorageActor.storageKey + "-" + this.layer.layerDef.id + "-" + neededIndex

View file

@ -1,8 +1,14 @@
import * as turf from '@turf/turf'
import {BBox} from "./BBox";
import togpx from "togpx"
import Constants from "../Models/Constants";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export class GeoOperations {
private static readonly _earthRadius = 6378137;
private static readonly _originShift = 2 * Math.PI * GeoOperations._earthRadius / 2;
static surfaceAreaInSqMeters(feature: any) {
return turf.area(feature);
}
@ -24,12 +30,12 @@ export class GeoOperations {
}
/**
* Returns the distance between the two points in kilometers
* Returns the distance between the two points in meters
* @param lonlat0
* @param lonlat1
*/
static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) {
return turf.distance(lonlat0, lonlat1)
return turf.distance(lonlat0, lonlat1) * 1000
}
/**
@ -237,7 +243,7 @@ export class GeoOperations {
* @param point Point defined as [lon, lat]
*/
public static nearestPoint(way, point: [number, number]) {
if(way.geometry.type === "Polygon"){
if (way.geometry.type === "Polygon") {
way = {...way}
way.geometry = {...way.geometry}
way.geometry.type = "LineString"
@ -292,10 +298,6 @@ export class GeoOperations {
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
}
private static readonly _earthRadius = 6378137;
private static readonly _originShift = 2 * Math.PI * GeoOperations._earthRadius / 2;
//Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] {
const lon = lonLat[0];
@ -316,10 +318,35 @@ export class GeoOperations {
return [x, y];
}
public static GeoJsonToWGS84(geojson){
public static GeoJsonToWGS84(geojson) {
return turf.toWgs84(geojson)
}
/**
* Tries to remove points which do not contribute much to the general outline.
* Points for which the angle is ~ 180° are removed
* @param coordinates
* @constructor
*/
public static SimplifyCoordinates(coordinates: [number, number][]) {
const newCoordinates = []
for (let i = 1; i < coordinates.length - 1; i++) {
const coordinate = coordinates[i];
const prev = coordinates[i - 1]
const next = coordinates[i + 1]
const b0 = turf.bearing(prev, coordinate, {final: true})
const b1 = turf.bearing(coordinate, next)
const diff = Math.abs(b1 - b0)
if (diff < 2) {
continue
}
newCoordinates.push(coordinate)
}
return newCoordinates
}
/**
* Calculates the intersection between two features.
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
@ -412,31 +439,176 @@ export class GeoOperations {
return undefined;
}
/**
* Tries to remove points which do not contribute much to the general outline.
* Points for which the angle is ~ 180° are removed
* @param coordinates
* @constructor
*/
public static SimplifyCoordinates(coordinates: [number, number][]){
const newCoordinates = []
for (let i = 1; i < coordinates.length - 1; i++){
const coordinate = coordinates[i];
const prev = coordinates[i - 1]
const next = coordinates[i + 1]
const b0 = turf.bearing(prev, coordinate, {final: true})
const b1 = turf.bearing(coordinate, next)
public static AsGpx(feature, generatedWithLayer?: LayerConfig){
const diff = Math.abs(b1 - b0)
if(diff < 2){
continue
const metadata = {}
const tags = feature.properties
if(generatedWithLayer !== undefined){
metadata["name"] = generatedWithLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt
metadata["desc"] = "Generated with MapComplete layer "+generatedWithLayer.id
if(tags._backend?.contains("openstreetmap")){
metadata["copyright"]= "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright"
metadata["author"] = tags["_last_edit:contributor"]
metadata["link"]= "https://www.openstreetmap.org/"+tags.id
metadata["time"] = tags["_last_edit:timestamp"]
}else{
metadata["time"] = new Date().toISOString()
}
newCoordinates.push(coordinate)
}
return newCoordinates
return togpx(feature, {
creator: "MapComplete "+Constants.vNumber,
metadata
})
}
public static IdentifieCommonSegments(coordinatess: [number,number][][] ): {
originalIndex: number,
segmentShardWith: number[],
coordinates: []
}[]{
// An edge. Note that the edge might be reversed to fix the sorting condition: start[0] < end[0] && (start[0] != end[0] || start[0] < end[1])
type edge = {start: [number, number], end: [number, number], intermediate: [number,number][], members: {index:number, isReversed: boolean}[]}
// The strategy:
// 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them
// 2. Join these edges back together - as long as their membership groups are the same
// 3. Convert to results
const allEdgesByKey = new Map<string, edge>()
for (let index = 0; index < coordinatess.length; index++){
const coordinates = coordinatess[index];
for (let i = 0; i < coordinates.length - 1; i++){
const c0 = coordinates[i];
const c1 = coordinates[i + 1]
const isReversed = (c0[0] > c1[0]) || (c0[0] == c1[0] && c0[1] > c1[1])
let key : string
if(isReversed){
key = ""+c1+";"+c0
}else{
key = ""+c0+";"+c1
}
const member = {index, isReversed}
if(allEdgesByKey.has(key)){
allEdgesByKey.get(key).members.push(member)
continue
}
let edge : edge;
if(!isReversed){
edge = {
start : c0,
end: c1,
members: [member],
intermediate: []
}
}else{
edge = {
start : c1,
end: c0,
members: [member],
intermediate: []
}
}
allEdgesByKey.set(key, edge)
}
}
// Lets merge them back together!
let didMergeSomething = false;
let allMergedEdges = Array.from(allEdgesByKey.values())
const allEdgesByStartPoint = new Map<string, edge[]>()
for (const edge of allMergedEdges) {
edge.members.sort((m0, m1) => m0.index - m1.index)
const kstart = edge.start+""
if(!allEdgesByStartPoint.has(kstart)){
allEdgesByStartPoint.set(kstart, [])
}
allEdgesByStartPoint.get(kstart).push(edge)
}
function membersAreCompatible(first:edge, second:edge): boolean{
// There must be an exact match between the members
if(first.members === second.members){
return true
}
if(first.members.length !== second.members.length){
return false
}
// Members are sorted and have the same length, so we can check quickly
for (let i = 0; i < first.members.length; i++) {
const m0 = first.members[i]
const m1 = second.members[i]
if(m0.index !== m1.index || m0.isReversed !== m1.isReversed){
return false
}
}
// Allrigth, they are the same, lets mark this permanently
second.members = first.members
return true
}
do{
didMergeSomething = false
// We use 'allMergedEdges' as our running list
const consumed = new Set<edge>()
for (const edge of allMergedEdges) {
// Can we make this edge longer at the end?
if(consumed.has(edge)){
continue
}
console.log("Considering edge", edge)
const matchingEndEdges = allEdgesByStartPoint.get(edge.end+"")
console.log("Matchign endpoints:", matchingEndEdges)
if(matchingEndEdges === undefined){
continue
}
for (let i = 0; i < matchingEndEdges.length; i++){
const endEdge = matchingEndEdges[i];
if(consumed.has(endEdge)){
continue
}
if(!membersAreCompatible(edge, endEdge)){
continue
}
// We can make the segment longer!
didMergeSomething = true
console.log("Merging ", edge, "with ", endEdge)
edge.intermediate.push(edge.end)
edge.end = endEdge.end
consumed.add(endEdge)
matchingEndEdges.splice(i, 1)
break;
}
}
allMergedEdges = allMergedEdges.filter(edge => !consumed.has(edge));
}while(didMergeSomething)
return []
}
}

View file

@ -32,7 +32,7 @@ export default class AllImageProviders {
return undefined;
}
const cacheKey = tags.data.id+tagKey
const cacheKey = tags.data.id + tagKey
const cached = this._cache.get(cacheKey)
if (cached !== undefined) {
return cached
@ -45,7 +45,7 @@ export default class AllImageProviders {
for (const imageProvider of AllImageProviders.ImageAttributionSource) {
let prefixes = imageProvider.defaultKeyPrefixes
if(tagKey !== undefined){
if (tagKey !== undefined) {
prefixes = tagKey
}
@ -54,11 +54,11 @@ export default class AllImageProviders {
})
allSources.push(singleSource)
singleSource.addCallbackAndRunD(_ => {
const all : ProvidedImage[] = [].concat(...allSources.map(source => source.data))
const all: ProvidedImage[] = [].concat(...allSources.map(source => source.data))
const uniq = []
const seen = new Set<string>()
for (const img of all) {
if(seen.has(img.url)){
if (seen.has(img.url)) {
continue
}
seen.add(img.url)

View file

@ -10,20 +10,15 @@ export default class GenericImageProvider extends ImageProvider {
this._valuePrefixBlacklist = valuePrefixBlacklist;
}
protected DownloadAttribution(url: string) {
return undefined
}
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
if (this._valuePrefixBlacklist.some(prefix => value.startsWith(prefix))) {
return []
}
try{
try {
new URL(value)
}catch (_){
} catch (_) {
// Not a valid URL
return []
}
@ -39,5 +34,9 @@ export default class GenericImageProvider extends ImageProvider {
return undefined;
}
protected DownloadAttribution(url: string) {
return undefined
}
}

View file

@ -27,8 +27,6 @@ export default abstract class ImageProvider {
public abstract SourceIcon(backlinkSource?: string): BaseUIElement;
protected abstract DownloadAttribution(url: string): Promise<LicenseInfo>;
/**
* Given a properies object, maps it onto _all_ the available pictures for this imageProvider
*/
@ -77,4 +75,6 @@ export default abstract class ImageProvider {
public abstract ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]>;
protected abstract DownloadAttribution(url: string): Promise<LicenseInfo>;
}

Some files were not shown because too many files have changed in this diff Show more