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: on:
push: push:
branches: [ develop, master ] branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '21 18 * * 3'
jobs: jobs:
analyze: analyze:

View file

@ -1,14 +1,33 @@
import * as known_layers from "../assets/generated/known_layers_and_themes.json" import * as known_layers from "../assets/generated/known_layers_and_themes.json"
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; 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 { export default class AllKnownLayers {
public static inited = (_ => {
WithContextLoader.getKnownTagRenderings = (id => AllKnownLayers.getTagRendering(id))
return true
})()
// Must be below the list... // Must be below the list...
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers(); public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson(); 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> { private static getSharedLayers(): Map<string, LayerConfig> {
const sharedLayers = new Map<string, LayerConfig>(); const sharedLayers = new Map<string, LayerConfig>();
for (const layer of known_layers.layers) { for (const layer of known_layers.layers) {
@ -16,7 +35,6 @@ export default class AllKnownLayers {
// @ts-ignore // @ts-ignore
const parsed = new LayerConfig(layer, "shared_layers") const parsed = new LayerConfig(layer, "shared_layers")
sharedLayers.set(layer.id, parsed); sharedLayers.set(layer.id, parsed);
sharedLayers[layer.id] = parsed;
} catch (e) { } catch (e) {
if (!Utils.runningFromConsole) { if (!Utils.runningFromConsole) {
console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e) console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e)
@ -48,7 +66,7 @@ export default class AllKnownLayers {
return sharedLayers; return sharedLayers;
} }
private static getSharedLayersJson(): Map<string, any> { private static getSharedLayersJson(): Map<string, LayerConfigJson> {
const sharedLayers = new Map<string, any>(); const sharedLayers = new Map<string, any>();
for (const layer of known_layers.layers) { for (const layer of known_layers.layers) {
sharedLayers.set(layer.id, layer); sharedLayers.set(layer.id, layer);
@ -57,5 +75,41 @@ export default class AllKnownLayers {
return sharedLayers; 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 * as known_themes from "../assets/generated/known_layers_and_themes.json"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; 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 { export class AllKnownLayouts {
@ -29,6 +33,50 @@ export class AllKnownLayouts {
return allLayers 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[] { private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] {
const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"]
const list = [] const list = []

View file

@ -38,7 +38,10 @@ export default class SharedTagRenderings {
dict.set(key, <TagRenderingConfigJson>icons[key]) dict.set(key, <TagRenderingConfigJson>icons[key])
} }
dict.forEach((value, key) => value.id = key)
return dict; 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 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 **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 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 The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme
### _lat, _lon ### _lat, _lon
@ -28,6 +31,7 @@ The latitude and longitude of the point (or centerpoint in the case of a way/are
### _layer ### _layer
@ -37,6 +41,7 @@ The layer-id to which this feature belongs. Note that this might be return any a
### _surface, _surface:ha ### _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 This is a lazy metatag and is only calculated when needed
### _length, _length:km ### _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 ### Theme-defined keys
@ -64,6 +71,7 @@ If 'units' is defined in the layoutConfig, then this metatagger will rewrite the
### _country ### _country
@ -73,6 +81,7 @@ The country code of the property (with latlon2country)
### _isOpen, _isOpen:description ### _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 This is a lazy metatag and is only calculated when needed
### _direction:numerical, _direction:leftright ### _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 ### _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 ### _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 ### 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 Calculating tags with Javascript
---------------------------------- ----------------------------------
@ -173,6 +187,7 @@ Some advanced functions are available on **feat** as well:
- [memberships](#memberships) - [memberships](#memberships)
- [get](#get) - [get](#get)
### distanceTo ### 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 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 0. feature OR featureID OR longitude
1. undefined OR latitude 1. undefined OR latitude
### overlapWith ### 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. 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) 0. ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)
### closest ### 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) 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 0. list of features or a layer name or '*' to get all features
### closestn ### 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) 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) 2. unique tag key (optional)
3. maxDistanceInMeters (optional) 3. maxDistanceInMeters (optional)
### memberships ### memberships
Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. 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 ### get
Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ... Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ...
0. key 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 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. If you have access to the repository, you can make a fork of an already existing branch and push this new branch to
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). github. This means that this branch will be _automatically built_ and be **deployed**
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. 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 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 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: To do this:
1. type `git remote add upstream git@github.com:pietervdvn/MapComplete` 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,7 +28,8 @@ To develop and build MapComplete, you
0. Make a fork and clone the repository. 0. Make a fork and clone the repository.
0. Install the nodejs version specified in [.tool-versions](./.tool-versions) 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>` - 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/). - 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 0. Install `npm`. Linux: `sudo apt install npm` (or your favourite package manager), Windows: install
nodeJS: https://nodejs.org/en/download/ nodeJS: https://nodejs.org/en/download/
@ -106,7 +107,8 @@ Try removing `node_modules`, `package-lock.json` and `.cache`
Misc setup 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 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) - [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) - 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 ### MetaTags
There are few tags available that are calculated for convenience - e.g. the country an object is located 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) 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 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! 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 Use the `filter`-functionality instead
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.
### Not reading the .JSON-specs ### Not reading the .JSON-specs

View file

@ -5,7 +5,7 @@
--> -->
<!-- Title: G Pages: 1 --> <!-- Title: G Pages: 1 -->
<svg width="664pt" height="566pt" <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"> 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)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 562)">
<title>G</title> <title>G</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-562 660.25,-562 660.25,4 -4,4"/> <polygon fill="white" stroke="transparent" points="-4,4 -4,-562 660.25,-562 660.25,4 -4,4"/>
@ -24,22 +24,30 @@
<!-- init&#45;&gt;denied --> <!-- init&#45;&gt;denied -->
<g id="edge1" class="edge"> <g id="edge1" class="edge">
<title>init&#45;&gt;denied</title> <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"/> <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"/> <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> <text text-anchor="middle" x="132.25" y="-405.8" font-family="Times,serif" font-size="14.00">geolocation
permanently denied
</text>
</g> </g>
<!-- getting_location --> <!-- getting_location -->
<g id="node3" class="node"> <g id="node3" class="node">
<title>getting_location</title> <title>getting_location</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-279" rx="85.29" ry="18"/> <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> <text text-anchor="middle" x="366.25" y="-275.3" font-family="Times,serif" font-size="14.00">
getting_location
</text>
</g> </g>
<!-- init&#45;&gt;getting_location --> <!-- init&#45;&gt;getting_location -->
<g id="edge2" class="edge"> <g id="edge2" class="edge">
<title>init&#45;&gt;getting_location</title> <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"/> <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"/> <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> <text text-anchor="middle" x="559.75" y="-405.8" font-family="Times,serif" font-size="14.00">previously
granted flag set
</text>
</g> </g>
<!-- idle --> <!-- idle -->
<g id="node4" class="node"> <g id="node4" class="node">
@ -50,94 +58,122 @@
<!-- init&#45;&gt;idle --> <!-- init&#45;&gt;idle -->
<g id="edge3" class="edge"> <g id="edge3" class="edge">
<title>init&#45;&gt;idle</title> <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"/> <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"/> <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> <text text-anchor="middle" x="321.75" y="-492.8" font-family="Times,serif" font-size="14.00">previously
granted flag unset
</text>
</g> </g>
<!-- location_found --> <!-- location_found -->
<g id="node6" class="node"> <g id="node6" class="node">
<title>location_found</title> <title>location_found</title>
<ellipse fill="none" stroke="black" cx="366.25" cy="-192" rx="77.99" ry="18"/> <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> <text text-anchor="middle" x="366.25" y="-188.3" font-family="Times,serif" font-size="14.00">
location_found
</text>
</g> </g>
<!-- getting_location&#45;&gt;location_found --> <!-- getting_location&#45;&gt;location_found -->
<g id="edge8" class="edge"> <g id="edge8" class="edge">
<title>getting_location&#45;&gt;location_found</title> <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"/> <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"/> <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> <text text-anchor="middle" x="417.25" y="-231.8" font-family="Times,serif" font-size="14.00">location
found
</text>
</g> </g>
<!-- request_permission --> <!-- request_permission -->
<g id="node5" class="node"> <g id="node5" class="node">
<title>request_permission</title> <title>request_permission</title>
<ellipse fill="none" stroke="black" cx="264.25" cy="-366" rx="102.08" ry="18"/> <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> <text text-anchor="middle" x="264.25" y="-362.3" font-family="Times,serif" font-size="14.00">
request_permission
</text>
</g> </g>
<!-- idle&#45;&gt;request_permission --> <!-- idle&#45;&gt;request_permission -->
<g id="edge4" class="edge"> <g id="edge4" class="edge">
<title>idle&#45;&gt;request_permission</title> <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"/> <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"/> <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> <text text-anchor="middle" x="371.75" y="-405.8" font-family="Times,serif" font-size="14.00">on click</text>
</g> </g>
<!-- request_permission&#45;&gt;denied --> <!-- request_permission&#45;&gt;denied -->
<g id="edge7" class="edge"> <g id="edge7" class="edge">
<title>request_permission&#45;&gt;denied</title> <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"/> <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"/> <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> <text text-anchor="middle" x="206.25" y="-318.8" font-family="Times,serif" font-size="14.00">permanently
denied
</text>
</g> </g>
<!-- request_permission&#45;&gt;getting_location --> <!-- request_permission&#45;&gt;getting_location -->
<g id="edge5" class="edge"> <g id="edge5" class="edge">
<title>request_permission&#45;&gt;getting_location</title> <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"/> <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"/> <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> <text text-anchor="middle" x="360.75" y="-318.8" font-family="Times,serif" font-size="14.00">granted (sets
flag)
</text>
</g> </g>
<!-- request_permission&#45;&gt;idle --> <!-- request_permission&#45;&gt;idle -->
<g id="edge6" class="edge"> <g id="edge6" class="edge">
<title>request_permission&#45;&gt;idle</title> <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"/> <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"/> <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> <text text-anchor="middle" x="294.75" y="-405.8" font-family="Times,serif" font-size="14.00">not granted
</text>
</g> </g>
<!-- open_lock --> <!-- open_lock -->
<g id="node7" class="node"> <g id="node7" class="node">
<title>open_lock</title> <title>open_lock</title>
<ellipse fill="none" stroke="black" cx="333.25" cy="-105" rx="55.79" ry="18"/> <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> <text text-anchor="middle" x="333.25" y="-101.3" font-family="Times,serif" font-size="14.00">open_lock
</text>
</g> </g>
<!-- location_found&#45;&gt;open_lock --> <!-- location_found&#45;&gt;open_lock -->
<g id="edge9" class="edge"> <g id="edge9" class="edge">
<title>location_found&#45;&gt;open_lock</title> <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"/> <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"/> <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> <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> </g>
<!-- open_lock&#45;&gt;location_found --> <!-- open_lock&#45;&gt;location_found -->
<g id="edge10" class="edge"> <g id="edge10" class="edge">
<title>open_lock&#45;&gt;location_found</title> <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"/> <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"/> <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> <text text-anchor="middle" x="305.25" y="-144.8" font-family="Times,serif" font-size="14.00">after 3 sec
</text>
</g> </g>
<!-- closed_lock --> <!-- closed_lock -->
<g id="node8" class="node"> <g id="node8" class="node">
<title>closed_lock</title> <title>closed_lock</title>
<ellipse fill="none" stroke="black" cx="454.25" cy="-18" rx="63.09" ry="18"/> <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> <text text-anchor="middle" x="454.25" y="-14.3" font-family="Times,serif" font-size="14.00">closed_lock
</text>
</g> </g>
<!-- open_lock&#45;&gt;closed_lock --> <!-- open_lock&#45;&gt;closed_lock -->
<g id="edge11" class="edge"> <g id="edge11" class="edge">
<title>open_lock&#45;&gt;closed_lock</title> <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"/> <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"/> <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> <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> </g>
<!-- closed_lock&#45;&gt;location_found --> <!-- closed_lock&#45;&gt;location_found -->
<g id="edge12" class="edge"> <g id="edge12" class="edge">
<title>closed_lock&#45;&gt;location_found</title> <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"/> <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"/> <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> <text text-anchor="middle" x="604.75" y="-101.3" font-family="Times,serif" font-size="14.00">on click</text>
</g> </g>

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

@ -6,13 +6,15 @@ Some highlights of new releases.
0.10 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: Some highlights are:
1. The addition of fallback overpass servers 1. The addition of fallback overpass servers
2. Fetching data from OSM directly (especially useful in the personal theme) 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. 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: Furthermore, it contains a few new themes and theme updates:
@ -31,9 +33,8 @@ Other various small improvements:
0.8 and 0.9 0.8 and 0.9
----------- -----------
Addition of filters per layer Addition of filters per layer Addition of a download-as-pdf for select themes Addition of a download-as-geojson and
Addition of a download-as-pdf for select themes download-as-csv 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 Available types for text fields
================================= =================================
@ -12,7 +13,7 @@ A basic string
## text ## 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 ## date
@ -29,6 +30,7 @@ A geographical length in meters (rounded at two points). Will give an extra mini
## wikidata ## wikidata
A wikidata identifier, e.g. Q42. A wikidata identifier, e.g. Q42.
### Helper arguments ### 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 removePostfixes | remove these snippets of text from the end of the passed string to search
### Example usage ### 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 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 ## opening_hours
Has extra elements to easily input when a POI is opened. Has extra elements to easily input when a POI is opened.
### Helper arguments ### 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 postfix | Piece of text that will always be added to the end of the generated opening hours
### Example usage ### Example usage
To add a conditional (based on time) access restriction: 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 ## 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 ### Special tag renderings
@ -27,14 +28,17 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam
### all_tags ### all_tags
Prints all key-value pairs of the object - used for debugging Prints all key-value pairs of the object - used for debugging
#### Example usage #### Example usage
`{all_tags()}` `{all_tags()}`
### image_carousel ### 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) 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... 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 #### Example usage
`{image_carousel(image,mapillary,image,wikidata,wikimedia_commons,image,image)}` `{image_carousel(image,mapillary,image,wikidata,wikimedia_commons,image,image)}`
### image_upload ### image_upload
Creates a button where a user can upload an image to IMGUR 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) 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 label | Add image | The text to show on the button
#### Example usage #### Example usage
`{image_upload(image,Add image)}` `{image_upload(image,Add image)}`
### wikipedia ### wikipedia
A box showing the corresponding wikipedia article - based on the wikidata tag 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 keyToShowWikipediaFor | wikidata | Use the wikidata entry from this key to show the wikipedia article for
#### Example usage #### 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 `{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 ### minimap
A small map showing the selected feature. 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 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. 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 #### Example usage
`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}` `{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`
### sided_minimap ### 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 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` side | _undefined_ | The side to show, either `left` or `right`
#### Example usage #### Example usage
`{sided_minimap(left)}` `{sided_minimap(left)}`
### reviews ### 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 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> 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 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 #### 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 `{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 ### opening_hours_table
Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'. 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__ 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__ postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__
#### Example usage #### 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)}` 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 ### 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)} 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 ; 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 path | _undefined_ | The path (or shorthand) that should be returned
#### Example usage #### 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)} {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 ### histogram
Create a histogram for a list of given values, read from the properties. 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 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` colors* | _undefined_ | (Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`
#### Example usage #### Example usage
`{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram `{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram
### share_link ### share_link
Creates a link that (attempts to) open the native 'share'-screen 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) url | _undefined_ | The url to share (default: current URL)
#### Example usage #### Example usage
{share_link()} to share the current page, {share_link(<some_url>)} to share the given url {share_link()} to share the current page, {share_link(<some_url>)} to share the given url
### canonical ### canonical
Converts a short, canonical value into the long, translated text 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 key | _undefined_ | The key of the tag to give the canonical text for
#### Example usage #### Example usage
{canonical(length)} will give 42 metre (in french) {canonical(length)} will give 42 metre (in french)
### import_button ### 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. 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 - 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) snap max distance | 5 | The maximum distance that this point will move to snap onto a layer (in meters)
#### Example usage #### Example usage
`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18,,5)}` `{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18,,5)}`
### multi_apply ### 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 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 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 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 #### 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)} {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 ### tag_apply
Shows a big button; clicking this button will apply certain tags onto the feature. 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 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 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 #### 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')", "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" "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", "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')", "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", "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": "" "value": ""
}, },
{ {
"key": "construction:amenity", "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": "" "value": ""
}, },
{ {
"key": "disused:amenity", "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": "" "value": ""
}, },
{ {
"key": "operational_status", "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.", "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": ""
},
{
"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')",
"value": "broken" "value": "broken"
}, },
{ {
"key": "amenity", "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" "value": "charging_station"
}, },
{ {
"key": "planned:amenity", "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" "value": "charging_station"
}, },
{ {
"key": "construction:amenity", "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": "" "value": ""
}, },
{ {
"key": "disused:amenity", "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": "" "value": ""
}, },
{ {
"key": "operational_status", "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": "" "value": ""
}, },
{ {
"key": "amenity", "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": "" "value": ""
}, },
{ {
"key": "planned:amenity", "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": "" "value": ""
}, },
{ {
"key": "construction:amenity", "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" "value": "charging_station"
}, },
{ {
"key": "disused:amenity", "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": "" "value": ""
}, },
{ {
"key": "operational_status", "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": "" "value": ""
}, },
{ {
"key": "amenity", "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": "" "value": ""
}, },
{ {
"key": "planned:amenity", "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": "" "value": ""
}, },
{ {
"key": "construction:amenity", "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": "" "value": ""
}, },
{ {
"key": "disused:amenity", "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" "value": "charging_station"
}, },
{ {
"key": "operational_status", "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": "" "value": ""
}, },
{ {
"key": "amenity", "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": "" "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", "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')", "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", "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" "value": "double"
}, },
{ {

View file

@ -966,13 +966,13 @@
}, },
{ {
"key": "location", "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')", "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": "underground" "value": "surface"
}, },
{ {
"key": "location", "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')", "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": "surface" "value": "rooftop"
}, },
{ {
"key": "location", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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')", "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" "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", "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')", "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", "key": "charge",
"description": "Layer 'Toilets' shows and asks freeform values for key 'charge' (in the MapComplete.osm.be theme 'Open Toilet Map')" "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", "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')", "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", "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" "value": "yes"
}, },
{ {

View file

@ -1,7 +1,7 @@
from datetime import datetime
from matplotlib import pyplot
import json import json
import sys import sys
from datetime import datetime
from matplotlib import pyplot
def pyplot_init(): def pyplot_init():
@ -17,6 +17,7 @@ def genKeys(data, type):
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) return list(keys)
def createPie(options): def createPie(options):
data = options["plot"]["count"] data = options["plot"]["count"]
keys = genKeys(data, options["interpetKeysAs"]) keys = genKeys(data, options["interpetKeysAs"])
@ -28,6 +29,7 @@ def createPie(options):
pyplot_init() pyplot_init()
pyplot.pie(values, labels=keys, startangle=(90 - 360 * first_pct / 2)) pyplot.pie(values, labels=keys, startangle=(90 - 360 * first_pct / 2))
def createBar(options): def createBar(options):
data = options["plot"]["count"] data = options["plot"]["count"]
keys = genKeys(data, options["interpetKeysAs"]) keys = genKeys(data, options["interpetKeysAs"])
@ -37,7 +39,6 @@ def createBar(options):
pyplot.legend() pyplot.legend()
pyplot_init() pyplot_init()
title = sys.argv[1] title = sys.argv[1]
pyplot.title = title pyplot.title = title

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

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 518 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 KiB

After

Width:  |  Height:  |  Size: 546 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 707 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 737 KiB

After

Width:  |  Height:  |  Size: 731 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 468 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

After

Width:  |  Height:  |  Size: 481 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 KiB

After

Width:  |  Height:  |  Size: 533 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before After
Before After

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. Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.
fs-userbadge 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_ 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 fs-search
----------- -----------
Disables/Enables the search bar The default value is _true_ Disables/Enables the search bar The default value is _true_
fs-background fs-background
--------------- ---------------
Disables/Enables the background layer control The default value is _true_ Disables/Enables the background layer control The default value is _true_
fs-filter fs-filter
----------- -----------
Disables/Enables the filter The default value is _true_ Disables/Enables the filter The default value is _true_
fs-add-new 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_ 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 fs-welcome-message
-------------------- --------------------
Disables/enables the help menu or welcome message The default value is _true_ Disables/enables the help menu or welcome message The default value is _true_
fs-iframe-popout 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_ 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 fs-more-quests
---------------- ----------------
Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_
fs-share-screen fs-share-screen
----------------- -----------------
Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_
fs-geolocation fs-geolocation
---------------- ----------------
Disables/Enables the geolocation button The default value is _true_ Disables/Enables the geolocation button The default value is _true_
fs-all-questions fs-all-questions
------------------ ------------------
Always show all questions The default value is _false_ Always show all questions The default value is _false_
fs-export fs-export
----------- -----------
Enable the export as GeoJSON and CSV button The default value is _false_ Enable the export as GeoJSON and CSV button The default value is _false_
fs-pdf fs-pdf
-------- --------
Enable the PDF download button The default value is _false_ Enable the PDF download button The default value is _false_
backend 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_ 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 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_ 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 debug
------- -------
If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ If true, shows some extra debugging help such as all the available tags on every object The default value is _false_
fake-user fake-user
----------- -----------
If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_ If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_
overpassUrl 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_ 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 overpassTimeout
----------------- -----------------
Set a different timeout (in seconds) for queries in overpass The default value is _30_ Set a different timeout (in seconds) for queries in overpass The default value is _30_
overpassMaxZoom overpassMaxZoom
----------------- -----------------
point to switch between OSM-api and overpass The default value is _17_ point to switch between OSM-api and overpass The default value is _17_
osmApiTileSize osmApiTileSize
---------------- ----------------
Tilesize when the OSM-API is used to fetch data within a BBOX The default value is _18_ Tilesize when the OSM-API is used to fetch data within a BBOX The default value is _18_
background background
------------ ------------
The id of the background layer to start with The default value is _osm_ The id of the background layer to start with The default value is _osm_
layer-<layer-id> 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,7 +5,9 @@ import Loc from "../../Models/Loc";
export interface AvailableBaseLayersObj { export interface AvailableBaseLayersObj {
readonly osmCarto: BaseLayer; readonly osmCarto: BaseLayer;
layerOverview: BaseLayer[]; layerOverview: BaseLayer[];
AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<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>;
} }

View file

@ -3,9 +3,9 @@ import {UIEventSource} from "../UIEventSource";
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc";
import {GeoOperations} from "../GeoOperations"; import {GeoOperations} from "../GeoOperations";
import * as editorlayerindex from "../../assets/editor-layer-index.json"; import * as editorlayerindex from "../../assets/editor-layer-index.json";
import * as L from "leaflet";
import {TileLayer} from "leaflet"; import {TileLayer} from "leaflet";
import * as X from "leaflet-providers"; import * as X from "leaflet-providers";
import * as L from "leaflet";
import {Utils} from "../../Utils"; import {Utils} from "../../Utils";
import {AvailableBaseLayersObj} from "./AvailableBaseLayers"; import {AvailableBaseLayersObj} from "./AvailableBaseLayers";
@ -28,102 +28,6 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
public layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex()); 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[] { private static LoadRasterIndex(): BaseLayer[] {
const layers: BaseLayer[] = [] const layers: BaseLayer[] = []
// @ts-ignore // @ts-ignore
@ -289,4 +193,100 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
subdomains: domains 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

@ -5,11 +5,22 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {QueryParameters} from "../Web/QueryParameters"; import {QueryParameters} from "../Web/QueryParameters";
import FeatureSource from "../FeatureSource/FeatureSource"; 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 { 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 * 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( constructor(
state: { state: {
currentGPSLocation: UIEventSource<Coordinates>, currentUserLocation: FeatureSource,
leafletMap: UIEventSource<any>, leafletMap: UIEventSource<any>,
layoutToUse: LayoutConfig, layoutToUse: LayoutConfig,
featureSwitchGeolocation: UIEventSource<boolean> featureSwitchGeolocation: UIEventSource<boolean>
} }
) { ) {
const currentGPSLocation = state.currentGPSLocation const currentGPSLocation = new UIEventSource<Coordinates>(undefined, "GPS-coordinate")
const leafletMap = state.leafletMap const leafletMap = state.leafletMap
const hasLocation = currentGPSLocation.map( const hasLocation = currentGPSLocation.map(
(location) => location !== undefined (location) => location !== undefined
@ -182,16 +193,21 @@ export default class GeoLocationHandler extends VariableUiElement {
} }
}) })
this.currentLocation = new StaticFeatureSource([], false) this.currentLocation = state.currentUserLocation
this._currentGPSLocation.addCallback((location) => { this._currentGPSLocation.addCallback((location) => {
self._previousLocationGrant.setData("granted"); self._previousLocationGrant.setData("granted");
const feature = { const feature = {
"type": "Feature", "type": "Feature",
properties: { properties: <GeoLocationPointProperties>{
id: "gps",
"user:location": "yes", "user:location": "yes",
"accuracy":location.accuracy, "date": new Date().toISOString(),
"latitude": location.latitude,
"longitude": location.longitude,
"speed": location.speed, "speed": location.speed,
"accuracy": location.accuracy,
"heading": location.heading,
"altitude": location.altitude
}, },
geometry: { geometry: {
type: "Point", type: "Point",

View file

@ -115,7 +115,6 @@ export default class OverpassFeatureSource implements FeatureSource {
let lastUsed = 0; let lastUsed = 0;
const layersToDownload = [] const layersToDownload = []
for (const layer of this.state.layoutToUse.layers) { for (const layer of this.state.layoutToUse.layers) {

View file

@ -10,7 +10,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
* Makes sure the hash shows the selected element and vice-versa. * Makes sure the hash shows the selected element and vice-versa.
*/ */
export default class SelectedFeatureHandler { 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 hash: UIEventSource<string>;
private readonly state: { private readonly state: {
selectedElement: UIEventSource<any>, selectedElement: UIEventSource<any>,

View file

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

View file

@ -28,7 +28,7 @@ export default class TitleHandler {
continue; continue;
} }
if (layer.source.osmTags.matchesProperties(tags)) { 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) const title = new TagRenderingAnswer(tagsSource, layer.title)
return new Combine([defaultTitle, " | ", title]).ConstructElement()?.innerText ?? defaultTitle; return new Combine([defaultTitle, " | ", title]).ConstructElement()?.innerText ?? defaultTitle;
} }

View file

@ -4,11 +4,11 @@ import {GeoOperations} from "./GeoOperations";
export class BBox { export class BBox {
static global: BBox = new BBox([[-180, -90], [180, 90]]);
readonly maxLat: number; readonly maxLat: number;
readonly maxLon: number; readonly maxLon: number;
readonly minLat: number; readonly minLat: number;
readonly minLon: number; readonly minLon: number;
static global: BBox = new BBox([[-180, -90], [180, 90]]);
constructor(coordinates) { constructor(coordinates) {
this.maxLat = -90; this.maxLat = -90;
@ -45,6 +45,17 @@ export class BBox {
return feature.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) * Constructs a tilerange which fully contains this bbox (thus might be a bit larger)
* @param zoomlevel * @param zoomlevel
@ -83,24 +94,6 @@ export class BBox {
return true; 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() { getEast() {
return this.maxLon return this.maxLon
} }
@ -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>()); 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 readonly state: { featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc> };
private lastUpdate: Date = undefined;
constructor(state: { featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc> }) { constructor(state: { featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc> }) {
this.state = state; this.state = state;
@ -21,8 +22,6 @@ export default class ContributorCount {
} }
private lastUpdate: Date = undefined;
private update(bbox: BBox) { private update(bbox: BBox) {
if (bbox === undefined) { if (bbox === undefined) {
return; return;

View file

@ -65,39 +65,6 @@ export default class DetermineLayout {
return [layoutToUse, undefined] 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( public static LoadLayoutFromHash(
userLayoutParam: UIEventSource<string> userLayoutParam: UIEventSource<string>
): [LayoutConfig, string] | null { ): [LayoutConfig, string] | null {
@ -166,4 +133,37 @@ export default class DetermineLayout {
.AttachTo("centermessage"); .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

@ -90,7 +90,7 @@ export class ExtraFunction {
private static readonly DistanceToFunc = new ExtraFunction( private static readonly DistanceToFunc = new ExtraFunction(
{ {
name: "distanceTo", 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"] args: ["feature OR featureID OR longitude", "undefined OR latitude"]
}, },
(featuresPerLayer, feature) => { (featuresPerLayer, feature) => {

View file

@ -5,11 +5,9 @@ import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from
import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource"; import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
import {UIEventSource} from "../UIEventSource"; import {UIEventSource} from "../UIEventSource";
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy"; import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
import FilteredLayer from "../../Models/FilteredLayer";
import MetaTagging from "../MetaTagging"; import MetaTagging from "../MetaTagging";
import RememberingSource from "./Sources/RememberingSource"; import RememberingSource from "./Sources/RememberingSource";
import OverpassFeatureSource from "../Actors/OverpassFeatureSource"; import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
import {Changes} from "../Osm/Changes";
import GeoJsonSource from "./Sources/GeoJsonSource"; import GeoJsonSource from "./Sources/GeoJsonSource";
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc";
import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor"; import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor";
@ -22,11 +20,10 @@ import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChan
import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator"; import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator";
import {BBox} from "../BBox"; import {BBox} from "../BBox";
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource"; import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
import {OsmConnection} from "../Osm/OsmConnection";
import {Tiles} from "../../Models/TileRange"; import {Tiles} from "../../Models/TileRange";
import TileFreshnessCalculator from "./TileFreshnessCalculator"; import TileFreshnessCalculator from "./TileFreshnessCalculator";
import {ElementStorage} from "../ElementStorage";
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource"; 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) public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
private readonly overpassUpdater: OverpassFeatureSource private readonly overpassUpdater: OverpassFeatureSource
private state: { private state: MapState;
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 readonly relationTracker: RelationsTracker private readonly relationTracker: RelationsTracker
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>; private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
@ -74,21 +59,7 @@ export default class FeaturePipeline {
constructor( constructor(
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
state: { state: MapState) {
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
}) {
this.state = state; this.state = state;
const self = this const self = this
@ -125,7 +96,7 @@ export default class FeaturePipeline {
const perLayerHierarchy = new Map<string, TileHierarchyMerger>() const perLayerHierarchy = new Map<string, TileHierarchyMerger>()
this.perLayerHierarchy = perLayerHierarchy 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 // This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
const srcFiltered = const srcFiltered =
new FilteringFeatureSource(state, src.tileIndex, new FilteringFeatureSource(state, src.tileIndex,
@ -135,7 +106,15 @@ export default class FeaturePipeline {
handleFeatureSource(srcFiltered) handleFeatureSource(srcFiltered)
self.somethingLoaded.setData(true) 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) // 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) { for (const filteredLayer of state.filteredLayers.data) {
@ -152,6 +131,26 @@ export default class FeaturePipeline {
continue; 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) { if (source.geojsonSource === undefined) {
// This is an OSM layer // This is an OSM layer
// We load the cached values and register them // We load the cached values and register them
@ -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 { private freshnessForVisibleLayers(z: number, x: number, y: number): Date {
let oldestDate = undefined; let oldestDate = undefined;
for (const flayer of this.state.filteredLayers.data) { 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 {UIEventSource} from "../UIEventSource";
import {Utils} from "../../Utils";
import FilteredLayer from "../../Models/FilteredLayer"; import FilteredLayer from "../../Models/FilteredLayer";
import {BBox} from "../BBox"; import {BBox} from "../BBox";

View file

@ -22,7 +22,7 @@ export default class PerLayerFeatureSourceSplitter {
const knownLayers = new Map<string, FeatureSourceForLayer & Tiled>() const knownLayers = new Map<string, FeatureSourceForLayer & Tiled>()
function update() { function update() {
const features = upstream.features.data; const features = upstream.features?.data;
if (features === undefined) { if (features === undefined) {
return; return;
} }
@ -35,6 +35,7 @@ export default class PerLayerFeatureSourceSplitter {
const featuresPerLayer = new Map<string, { feature, freshness } []>(); const featuresPerLayer = new Map<string, { feature, freshness } []>();
const noLayerFound = [] const noLayerFound = []
function addTo(layer: FilteredLayer, feature: { feature, freshness }) { function addTo(layer: FilteredLayer, feature: { feature, freshness }) {
const id = layer.layerDef.id const id = layer.layerDef.id
const list = featuresPerLayer.get(id) const list = featuresPerLayer.get(id)

View file

@ -11,9 +11,9 @@ import {ChangeDescription, ChangeDescriptionTools} from "../../Osm/Actions/Chang
export default class ChangeGeometryApplicator implements FeatureSourceForLayer { export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name: string; public readonly name: string;
public readonly layer: FilteredLayer
private readonly source: IndexedFeatureSource; private readonly source: IndexedFeatureSource;
private readonly changes: Changes; private readonly changes: Changes;
public readonly layer: FilteredLayer
constructor(source: (IndexedFeatureSource & FeatureSourceForLayer), changes: Changes) { constructor(source: (IndexedFeatureSource & FeatureSourceForLayer), changes: Changes) {
this.source = source; this.source = source;

View file

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

View file

@ -18,6 +18,8 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource<any>, locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource<any>,
allElements: ElementStorage allElements: ElementStorage
}; };
private readonly _alreadyRegistered = new Set<UIEventSource<any>>();
private readonly _is_dirty = new UIEventSource(false)
constructor( constructor(
state: { state: {
@ -55,24 +57,6 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this.update(); 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() { public update() {
const self = this; const self = this;
const layer = this.upstream.layer; const layer = this.upstream.layer;
@ -116,4 +100,19 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this._is_dirty.setData(false) 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 features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name; public readonly name;
public readonly isOsmCache: boolean public readonly isOsmCache: boolean
private readonly seenids: Set<string> = new Set<string>()
public readonly layer: FilteredLayer; public readonly layer: FilteredLayer;
public readonly tileIndex public readonly tileIndex
public readonly bbox; public readonly bbox;
private readonly seenids: Set<string> = new Set<string>()
/** /**
* Only used if the actual source is a tiled geojson. * Only used if the actual source is a tiled geojson.
* A big feature might be contained in multiple tiles. * A big feature might be contained in multiple tiles.

View file

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

View file

@ -4,17 +4,18 @@ import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {BBox} from "../../BBox"; import {BBox} from "../../BBox";
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { 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 name: string = "SimpleFeatureSource";
public readonly layer: FilteredLayer; public readonly layer: FilteredLayer;
public readonly bbox: BBox = BBox.global; public readonly bbox: BBox = BBox.global;
public readonly tileIndex: number; 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.name = "SimpleFeatureSource(" + layer.layerDef.id + ")"
this.layer = layer this.layer = layer
this.tileIndex = tileIndex ?? 0; this.tileIndex = tileIndex ?? 0;
this.bbox = BBox.fromTileIndex(this.tileIndex) this.bbox = BBox.fromTileIndex(this.tileIndex)
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([]);
} }
} }

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 * A tiled source which dynamically loads the required tiles at a fixed zoom level
*/ */
export default class DynamicTileSource implements TileHierarchy<FeatureSourceForLayer & Tiled> { export default class DynamicTileSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
private readonly _loadedTiles = new Set<number>();
public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled>; public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled>;
private readonly _loadedTiles = new Set<number>();
constructor( constructor(
layer: FilteredLayer, layer: FilteredLayer,

View file

@ -13,9 +13,10 @@ import {Or} from "../../Tags/Or";
import {TagsFilter} from "../../Tags/TagsFilter"; import {TagsFilter} from "../../Tags/TagsFilter";
export default class OsmFeatureSource { export default class OsmFeatureSource {
private readonly _backend: string;
public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false) 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 filteredLayers: UIEventSource<FilteredLayer[]>;
private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void; private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void;
private isActive: UIEventSource<boolean>; private isActive: UIEventSource<boolean>;
@ -28,11 +29,8 @@ export default class OsmFeatureSource {
}, },
markTileVisited?: (tileId: number) => void markTileVisited?: (tileId: number) => void
}; };
public readonly downloadedTiles = new Set<number>()
private readonly allowedTags: TagsFilter; private readonly allowedTags: TagsFilter;
public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = []
constructor(options: { constructor(options: {
handleTile: (tile: FeatureSourceForLayer & Tiled) => void; handleTile: (tile: FeatureSourceForLayer & Tiled) => void;
isActive: UIEventSource<boolean>, isActive: UIEventSource<boolean>,

View file

@ -17,11 +17,8 @@ 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. 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. In order to keep thins snappy, they are distributed over a tiled database per layer.
## Notes ## Notes
`cached-featuresbookcases` is the old key used `cahced-features{themeid}` and should be cleaned up `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> { export class TileHierarchyMerger implements TileHierarchy<FeatureSourceForLayer & Tiled> {
public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, 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; public readonly layer: FilteredLayer;
private readonly sources: Map<number, UIEventSource<FeatureSource[]>> = new Map<number, UIEventSource<FeatureSource[]>>();
private _handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource, index: number) => void; private _handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource, index: number) => void;
constructor(layer: FilteredLayer, handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled, index: number) => void) { constructor(layer: FilteredLayer, handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled, index: number) => void) {

View file

@ -1,6 +1,5 @@
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
import {UIEventSource} from "../../UIEventSource"; import {UIEventSource} from "../../UIEventSource";
import {Utils} from "../../../Utils";
import FilteredLayer from "../../../Models/FilteredLayer"; import FilteredLayer from "../../../Models/FilteredLayer";
import TileHierarchy from "./TileHierarchy"; import TileHierarchy from "./TileHierarchy";
import {Tiles} from "../../../Models/TileRange"; import {Tiles} from "../../../Models/TileRange";
@ -28,13 +27,13 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource,
public readonly containedIds: UIEventSource<Set<string>> public readonly containedIds: UIEventSource<Set<string>>
public readonly bbox: BBox; public readonly bbox: BBox;
public readonly tileIndex: number;
private upper_left: TiledFeatureSource private upper_left: TiledFeatureSource
private upper_right: TiledFeatureSource private upper_right: TiledFeatureSource
private lower_left: TiledFeatureSource private lower_left: TiledFeatureSource
private lower_right: TiledFeatureSource private lower_right: TiledFeatureSource
private readonly maxzoom: number; private readonly maxzoom: number;
private readonly options: TiledFeatureSourceOptions private readonly options: TiledFeatureSourceOptions
public readonly tileIndex: number;
private constructor(z: number, x: number, y: number, parent: TiledFeatureSource, options?: TiledFeatureSourceOptions) { private constructor(z: number, x: number, y: number, parent: TiledFeatureSource, options?: TiledFeatureSourceOptions) {
this.z = z; this.z = z;

View file

@ -13,44 +13,6 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
private readonly handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void; private readonly handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void;
private readonly undefinedTiles: Set<number>; 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, constructor(layer: FilteredLayer,
handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void, handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
state: { 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) { private loadTile(neededIndex: number) {
try { try {
const key = SaveTileToLocalStorageActor.storageKey + "-" + this.layer.layerDef.id + "-" + neededIndex const key = SaveTileToLocalStorageActor.storageKey + "-" + this.layer.layerDef.id + "-" + neededIndex

View file

@ -1,8 +1,14 @@
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import {BBox} from "./BBox"; import {BBox} from "./BBox";
import togpx from "togpx"
import Constants from "../Models/Constants";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export class GeoOperations { export class GeoOperations {
private static readonly _earthRadius = 6378137;
private static readonly _originShift = 2 * Math.PI * GeoOperations._earthRadius / 2;
static surfaceAreaInSqMeters(feature: any) { static surfaceAreaInSqMeters(feature: any) {
return turf.area(feature); 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 lonlat0
* @param lonlat1 * @param lonlat1
*/ */
static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) { static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) {
return turf.distance(lonlat0, lonlat1) return turf.distance(lonlat0, lonlat1) * 1000
} }
/** /**
@ -292,10 +298,6 @@ export class GeoOperations {
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n") 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 //Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] { public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] {
const lon = lonLat[0]; const lon = lonLat[0];
@ -320,6 +322,31 @@ export class GeoOperations {
return turf.toWgs84(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. * 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 * 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; return undefined;
} }
/** public static AsGpx(feature, generatedWithLayer?: LayerConfig){
* 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) const metadata = {}
if(diff < 2){ 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()
}
}
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 continue
} }
newCoordinates.push(coordinate)
let edge : edge;
if(!isReversed){
edge = {
start : c0,
end: c1,
members: [member],
intermediate: []
} }
return newCoordinates }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

@ -10,11 +10,6 @@ export default class GenericImageProvider extends ImageProvider {
this._valuePrefixBlacklist = valuePrefixBlacklist; this._valuePrefixBlacklist = valuePrefixBlacklist;
} }
protected DownloadAttribution(url: string) {
return undefined
}
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
if (this._valuePrefixBlacklist.some(prefix => value.startsWith(prefix))) { if (this._valuePrefixBlacklist.some(prefix => value.startsWith(prefix))) {
@ -39,5 +34,9 @@ export default class GenericImageProvider extends ImageProvider {
return undefined; 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; 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 * 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>[]>; public abstract ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]>;
protected abstract DownloadAttribution(url: string): Promise<LicenseInfo>;
} }

View file

@ -8,9 +8,8 @@ import {LicenseInfo} from "./LicenseInfo";
export class Imgur extends ImageProvider { export class Imgur extends ImageProvider {
public static readonly defaultValuePrefix = ["https://i.imgur.com"] public static readonly defaultValuePrefix = ["https://i.imgur.com"]
public readonly defaultKeyPrefixes: string[] = ["image"];
public static readonly singleton = new Imgur(); public static readonly singleton = new Imgur();
public readonly defaultKeyPrefixes: string[] = ["image"];
private constructor() { private constructor() {
super(); super();
@ -89,6 +88,17 @@ export class Imgur extends ImageProvider {
return undefined; return undefined;
} }
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
if (Imgur.defaultValuePrefix.some(prefix => value.startsWith(prefix))) {
return [Promise.resolve({
url: value,
key: key,
provider: this
})]
}
return []
}
protected DownloadAttribution: (url: string) => Promise<LicenseInfo> = async (url: string) => { protected DownloadAttribution: (url: string) => Promise<LicenseInfo> = async (url: string) => {
const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0]; const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0];
@ -112,16 +122,5 @@ export class Imgur extends ImageProvider {
return licenseInfo return licenseInfo
} }
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
if (Imgur.defaultValuePrefix.some(prefix => value.startsWith(prefix))) {
return [Promise.resolve({
url: value,
key: key,
provider: this
})]
}
return []
}
} }

View file

@ -6,9 +6,8 @@ export default class ImgurUploader {
public readonly queue: UIEventSource<string[]> = new UIEventSource<string[]>([]); public readonly queue: UIEventSource<string[]> = new UIEventSource<string[]>([]);
public readonly failed: UIEventSource<string[]> = new UIEventSource<string[]>([]); public readonly failed: UIEventSource<string[]> = new UIEventSource<string[]>([]);
public readonly success: UIEventSource<string[]> = new UIEventSource<string[]>([]); public readonly success: UIEventSource<string[]> = new UIEventSource<string[]>([]);
private readonly _handleSuccessUrl: (string) => void;
public maxFileSizeInMegabytes = 10; public maxFileSizeInMegabytes = 10;
private readonly _handleSuccessUrl: (string) => void;
constructor(handleSuccessUrl: (string) => void) { constructor(handleSuccessUrl: (string) => void) {
this._handleSuccessUrl = handleSuccessUrl; this._handleSuccessUrl = handleSuccessUrl;

View file

@ -7,48 +7,31 @@ import Constants from "../../Models/Constants";
export class Mapillary extends ImageProvider { export class Mapillary extends ImageProvider {
defaultKeyPrefixes = ["mapillary","image"]
public static readonly singleton = new Mapillary(); public static readonly singleton = new Mapillary();
private static readonly valuePrefix = "https://a.mapillary.com" private static readonly valuePrefix = "https://a.mapillary.com"
public static readonly valuePrefixes = [Mapillary.valuePrefix, "http://mapillary.com", "https://mapillary.com", "http://www.mapillary.com", "https://www.mapillary.com"] public static readonly valuePrefixes = [Mapillary.valuePrefix, "http://mapillary.com", "https://mapillary.com", "http://www.mapillary.com", "https://www.mapillary.com"]
defaultKeyPrefixes = ["mapillary", "image"]
private static ExtractKeyFromURL(value: string, failIfNoMath = false): { /**
key: string, * Returns the correct key for API v4.0
isApiv4?: boolean */
} { private static ExtractKeyFromURL(value: string): number {
let key: string;
if (value.startsWith(Mapillary.valuePrefix)) {
const key = value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1)
return {key: key, isApiv4: !isNaN(Number(key))};
}
const newApiFormat = value.match(/https?:\/\/www.mapillary.com\/app\/\?pKey=([0-9]*)/) const newApiFormat = value.match(/https?:\/\/www.mapillary.com\/app\/\?pKey=([0-9]*)/)
if (newApiFormat !== null) { if (newApiFormat !== null) {
return {key: newApiFormat[1], isApiv4: true} key = newApiFormat[1]
} else if (value.startsWith(Mapillary.valuePrefix)) {
key = value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1)
} }
const mapview = value.match(/https?:\/\/www.mapillary.com\/map\/im\/(.*)/) const keyAsNumber = Number(key)
if (mapview !== null) { if (!isNaN(keyAsNumber)) {
const key = mapview[1] return keyAsNumber
return {key: key, isApiv4: !isNaN(Number(key))};
} }
return undefined
if (value.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
// Extract the key of the image
value = value.substring("https://www.mapillary.com/map/im/".length);
}
const matchApi = value.match(/https?:\/\/images.mapillary.com\/([^/]*)(&.*)?/)
if (matchApi !== null) {
return {key: matchApi[1]};
}
if(failIfNoMath){
return undefined;
}
return {key: value, isApiv4: !isNaN(Number(value))};
} }
SourceIcon(backlinkSource?: string): BaseUIElement { SourceIcon(backlinkSource?: string): BaseUIElement {
@ -59,22 +42,21 @@ export class Mapillary extends ImageProvider {
return [this.PrepareUrlAsync(key, value)] return [this.PrepareUrlAsync(key, value)]
} }
protected async DownloadAttribution(url: string): Promise<LicenseInfo> {
const license = new LicenseInfo()
license.artist = "Contributor name unavailable";
license.license = "CC BY-SA 4.0";
// license.license = "Creative Commons Attribution-ShareAlike 4.0 International License";
license.attributionRequired = true;
return license
}
private async PrepareUrlAsync(key: string, value: string): Promise<ProvidedImage> { private async PrepareUrlAsync(key: string, value: string): Promise<ProvidedImage> {
const failIfNoMatch = key.indexOf("mapillary") < 0 const mapillaryId = Mapillary.ExtractKeyFromURL(value)
const keyV = Mapillary.ExtractKeyFromURL(value, failIfNoMatch) if (mapillaryId === undefined) {
if(keyV === undefined){
return undefined; return undefined;
} }
if (!keyV.isApiv4) {
const url = `https://images.mapillary.com/${keyV.key}/thumb-640.jpg?client_id=${Constants.mapillary_client_token_v3}`
return {
url: url,
provider: this,
key: key
}
} else {
const mapillaryId = keyV.key;
const metadataUrl = 'https://graph.mapillary.com/' + mapillaryId + '?fields=thumb_1024_url&&access_token=' + Constants.mapillary_client_token_v4; const metadataUrl = 'https://graph.mapillary.com/' + mapillaryId + '?fields=thumb_1024_url&&access_token=' + Constants.mapillary_client_token_v4;
const response = await Utils.downloadJson(metadataUrl) const response = await Utils.downloadJson(metadataUrl)
const url = <string>response["thumb_1024_url"]; const url = <string>response["thumb_1024_url"];
@ -85,29 +67,3 @@ export class Mapillary extends ImageProvider {
} }
} }
} }
protected async DownloadAttribution(url: string): Promise<LicenseInfo> {
const keyV = Mapillary.ExtractKeyFromURL(url)
if (keyV.isApiv4) {
const license = new LicenseInfo()
license.artist = "Contributor name unavailable";
license.license = "CC BY-SA 4.0";
// license.license = "Creative Commons Attribution-ShareAlike 4.0 International License";
license.attributionRequired = true;
return license
}
const key = keyV.key
const metadataURL = `https://a.mapillary.com/v3/images/${key}?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`
const data = await Utils.downloadJson(metadataURL)
const license = new LicenseInfo();
license.artist = data.properties?.username;
license.licenseShortName = "CC BY-SA 4.0";
license.license = "Creative Commons Attribution-ShareAlike 4.0 International License";
license.attributionRequired = true;
return license
}
}

View file

@ -1,4 +1,3 @@
import {Utils} from "../../Utils";
import ImageProvider, {ProvidedImage} from "./ImageProvider"; import ImageProvider, {ProvidedImage} from "./ImageProvider";
import BaseUIElement from "../../UI/BaseUIElement"; import BaseUIElement from "../../UI/BaseUIElement";
import Svg from "../../Svg"; import Svg from "../../Svg";
@ -7,10 +6,6 @@ import Wikidata from "../Web/Wikidata";
export class WikidataImageProvider extends ImageProvider { export class WikidataImageProvider extends ImageProvider {
public SourceIcon(backlinkSource?: string): BaseUIElement {
throw Svg.wikidata_svg();
}
public static readonly singleton = new WikidataImageProvider() public static readonly singleton = new WikidataImageProvider()
public readonly defaultKeyPrefixes = ["wikidata"] public readonly defaultKeyPrefixes = ["wikidata"]
@ -18,8 +13,8 @@ export class WikidataImageProvider extends ImageProvider {
super() super()
} }
protected DownloadAttribution(url: string): Promise<any> { public SourceIcon(backlinkSource?: string): BaseUIElement {
throw new Error("Method not implemented; shouldn't be needed!"); throw Svg.wikidata_svg();
} }
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
@ -51,4 +46,8 @@ export class WikidataImageProvider extends ImageProvider {
return allImages return allImages
} }
protected DownloadAttribution(url: string): Promise<any> {
throw new Error("Method not implemented; shouldn't be needed!");
}
} }

View file

@ -12,10 +12,10 @@ import Wikimedia from "../Web/Wikimedia";
export class WikimediaImageProvider extends ImageProvider { export class WikimediaImageProvider extends ImageProvider {
private readonly commons_key = "wikimedia_commons"
public readonly defaultKeyPrefixes = [this.commons_key,"image"]
public static readonly singleton = new WikimediaImageProvider(); public static readonly singleton = new WikimediaImageProvider();
public static readonly commonsPrefixes = ["https://commons.wikimedia.org/wiki/", "https://upload.wikimedia.org", "File:"] public static readonly commonsPrefixes = ["https://commons.wikimedia.org/wiki/", "https://upload.wikimedia.org", "File:"]
private readonly commons_key = "wikimedia_commons"
public readonly defaultKeyPrefixes = [this.commons_key, "image"]
private constructor() { private constructor() {
super(); super();
@ -30,6 +30,40 @@ export class WikimediaImageProvider extends ImageProvider {
} }
private static PrepareUrl(value: string): string {
if (value.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) {
return value;
}
return (`https://commons.wikimedia.org/wiki/Special:FilePath/${encodeURIComponent(value)}?width=500&height=400`)
}
private static startsWithCommonsPrefix(value: string): boolean {
return WikimediaImageProvider.commonsPrefixes.some(prefix => value.startsWith(prefix))
}
private static removeCommonsPrefix(value: string): string {
if (value.startsWith("https://upload.wikimedia.org/")) {
value = value.substring(value.lastIndexOf("/") + 1)
value = decodeURIComponent(value)
if (!value.startsWith("File:")) {
value = "File:" + value
}
return value;
}
for (const prefix of WikimediaImageProvider.commonsPrefixes) {
if (value.startsWith(prefix)) {
let part = value.substr(prefix.length)
if (prefix.startsWith("http")) {
part = decodeURIComponent(part)
}
return part
}
}
return value;
}
SourceIcon(backlink: string): BaseUIElement { SourceIcon(backlink: string): BaseUIElement {
const img = Svg.wikimedia_commons_white_svg() const img = Svg.wikimedia_commons_white_svg()
.SetStyle("width:2em;height: 2em"); .SetStyle("width:2em;height: 2em");
@ -44,12 +78,38 @@ export class WikimediaImageProvider extends ImageProvider {
} }
private static PrepareUrl(value: string): string { public PrepUrl(value: string): ProvidedImage {
const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value)
value = WikimediaImageProvider.removeCommonsPrefix(value)
if (value.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { if (value.startsWith("File:")) {
return value; return this.UrlForImage(value)
} }
return (`https://commons.wikimedia.org/wiki/Special:FilePath/${encodeURIComponent(value)}?width=500&height=400`)
// We do a last effort and assume this is a file
return this.UrlForImage("File:" + value)
}
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value)
if (key !== undefined && key !== this.commons_key && !hasCommonsPrefix) {
return []
}
value = WikimediaImageProvider.removeCommonsPrefix(value)
if (value.startsWith("Category:")) {
const urls = await Wikimedia.GetCategoryContents(value)
return urls.filter(url => url.startsWith("File:")).map(image => Promise.resolve(this.UrlForImage(image)))
}
if (value.startsWith("File:")) {
return [Promise.resolve(this.UrlForImage(value))]
}
if (value.startsWith("http")) {
// PRobably an error
return []
}
// We do a last effort and assume this is a file
return [Promise.resolve(this.UrlForImage("File:" + value))]
} }
protected async DownloadAttribution(filename: string): Promise<LicenseInfo> { protected async DownloadAttribution(filename: string): Promise<LicenseInfo> {
@ -104,66 +164,6 @@ export class WikimediaImageProvider extends ImageProvider {
return {url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this} return {url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this}
} }
private static startsWithCommonsPrefix(value: string): boolean{
return WikimediaImageProvider.commonsPrefixes.some(prefix => value.startsWith(prefix))
}
private static removeCommonsPrefix(value: string): string{
if(value.startsWith("https://upload.wikimedia.org/")){
value = value.substring(value.lastIndexOf("/") + 1)
value = decodeURIComponent(value)
if(!value.startsWith("File:")){
value = "File:"+value
}
return value;
}
for (const prefix of WikimediaImageProvider.commonsPrefixes) {
if(value.startsWith(prefix)){
let part = value.substr(prefix.length)
if(prefix.startsWith("http")){
part = decodeURIComponent(part)
}
return part
}
}
return value;
}
public PrepUrl(value: string): ProvidedImage {
const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value)
value = WikimediaImageProvider.removeCommonsPrefix(value)
if (value.startsWith("File:")) {
return this.UrlForImage(value)
}
// We do a last effort and assume this is a file
return this.UrlForImage("File:" + value)
}
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value)
if(key !== undefined && key !== this.commons_key && !hasCommonsPrefix){
return []
}
value = WikimediaImageProvider.removeCommonsPrefix(value)
if (value.startsWith("Category:")) {
const urls = await Wikimedia.GetCategoryContents(value)
return urls.filter(url => url.startsWith("File:")).map(image => Promise.resolve(this.UrlForImage(image)))
}
if (value.startsWith("File:")) {
return [Promise.resolve(this.UrlForImage(value))]
}
if (value.startsWith("http")) {
// PRobably an error
return []
}
// We do a last effort and assume this is a file
return [Promise.resolve(this.UrlForImage("File:" + value))]
}
} }

View file

@ -147,7 +147,8 @@ export default class MetaTagging {
} }
} }
}} ) }
})
} }

View file

@ -20,7 +20,11 @@ export interface ChangeDescription {
/** /**
* THe motivation for the change, e.g. 'deleted because does not exist anymore' * THe motivation for the change, e.g. 'deleted because does not exist anymore'
*/ */
specialMotivation?: string specialMotivation?: string,
/**
* Added by Changes.ts
*/
distanceToObject?: number
}, },
/** /**

View file

@ -11,7 +11,7 @@ export default class ChangeLocationAction extends OsmChangeAction {
theme: string, theme: string,
reason: string reason: string
}) { }) {
super(); super(id,true);
if (!id.startsWith("node/")) { if (!id.startsWith("node/")) {
throw "Invalid ID: only 'node/number' is accepted" throw "Invalid ID: only 'node/number' is accepted"
} }

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