diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..9615220ed --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +liberapay: pietervdvn diff --git a/Customizations/SharedTagRenderings.ts b/Customizations/SharedTagRenderings.ts index 035a61dcc..0f47ef9a9 100644 --- a/Customizations/SharedTagRenderings.ts +++ b/Customizations/SharedTagRenderings.ts @@ -15,7 +15,7 @@ export default class SharedTagRenderings { const d = new Map() for (const key of Array.from(configJsons.keys())) { try { - d.set(key, new TagRenderingConfig(configJsons.get(key), undefined, `SharedTagRenderings.${key}`)) + d.set(key, new TagRenderingConfig(configJsons.get(key), `SharedTagRenderings.${key}`)) } catch (e) { if (!Utils.runningFromConsole) { console.error("BUG: could not parse", key, " from questions.json or icons.json - this error happened during the build step of the SharedTagRenderings", e) diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index b9bfe2104..02345f1b7 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -100,7 +100,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 +### _last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number, _backend @@ -109,6 +109,15 @@ Information about the last edit of this object. +### sidewalk:left, sidewalk:right, generic_key:left:property, generic_key:right:property + + + +Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined + + + + Calculating tags with Javascript ---------------------------------- @@ -162,6 +171,7 @@ Some advanced functions are available on **feat** as well: - closest - closestn - memberships + - get ### distanceTo @@ -173,7 +183,7 @@ Some advanced functions are available on **feat** as well: ### overlapWith Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point. - +The resulting list is sorted in descending order by overlap. The feature with the most overlap will thus be the first in the list For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')` 0. ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap) @@ -202,4 +212,10 @@ If a 'unique tag key' is given, the tag with this key will only appear once (e.g For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')` + +### get + + Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ... + + 0. key Generated from SimpleMetaTagger, ExtraFunction \ No newline at end of file diff --git a/Docs/SpecialInputElements.md b/Docs/SpecialInputElements.md index 3c05b4c87..d6648fa3b 100644 --- a/Docs/SpecialInputElements.md +++ b/Docs/SpecialInputElements.md @@ -1,4 +1,8 @@ -# Available types for text fields + + Available types for text fields +================================= + + The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them @@ -24,7 +28,44 @@ A geographical length in meters (rounded at two points). Will give an extra mini ## wikidata -A wikidata identifier, e.g. Q42. Input helper arguments: [ key: the value of this tag will initialize search (default: name), options: { removePrefixes: string[], removePostfixes: string[] } these prefixes and postfixes will be removed from the initial search value] +A wikidata identifier, e.g. Q42. +### Helper arguments + + + +name | doc +------ | ----- +key | the value of this tag will initialize search (default: name) +options | A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`. + +subarg | doc +-------- | ----- +removePrefixes | remove these snippets of text from the start of the passed string to search +removePostfixes | remove these snippets of text from the end of the passed string to search + + +### Example usage + + The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name + +``` +"freeform": { + "key": "name:etymology:wikidata", + "type": "wikidata", + "helperArgs": [ + "name", + { + "removePostfixes": [ + "street", + "boulevard", + "path", + "square", + "plaza", + ] + } + ] +} +``` ## int @@ -60,7 +101,40 @@ A phone number ## 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 + + + +name | doc +------ | ----- +options | A JSON-object of type `{ prefix: string, postfix: string }`. + +subarg | doc +-------- | ----- +prefix | Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse +postfix | Piece of text that will always be added to the end of the generated opening hours + + +### Example usage + + To add a conditional (based on time) access restriction: + +``` + +"freeform": { + "key": "access:conditional", + "type": "opening_hours", + "helperArgs": [ + { + "prefix":"no @ (", + "postfix":")" + } + ] +} +``` + +*Don't forget to pass the prefix and postfix in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )` ## color diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index 7fff50797..af4a3948b 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -1,24 +1,53 @@ ### Special tag renderings - In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's. General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_fcs need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args + + +In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's. + +General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args + + + + - [all_tags](#all_tags) + - [image_carousel](#image_carousel) + - [image_upload](#image_upload) + - [wikipedia](#wikipedia) + - [minimap](#minimap) + - [sided_minimap](#sided_minimap) + - [reviews](#reviews) + - [opening_hours_table](#opening_hours_table) + - [live](#live) + - [histogram](#histogram) + - [share_link](#share_link) + - [canonical](#canonical) + - [import_button](#import_button) + - [multi_apply](#multi_apply) + - [tag_apply](#tag_apply) + + + ### all_tags Prints all key-value pairs of the object - used for debugging #### Example usage - `{all_tags()}` + `{all_tags()}` + + ### image_carousel Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links) name | default | description ------ | --------- | ------------- -image key/prefix (multiple values allowed if comma-seperated) | image | The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, 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 image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... #### Example usage - `{image_carousel(image)}` + `{image_carousel(image,mapillary,image,wikidata,wikimedia_commons,image,image)}` + + ### image_upload Creates a button where a user can upload an image to IMGUR @@ -30,7 +59,9 @@ label | Add image | The text to show on the button #### Example usage - `{image_upload(image,Add image)}` + `{image_upload(image,Add image)}` + + ### wikipedia A box showing the corresponding wikipedia article - based on the wikidata tag @@ -41,10 +72,12 @@ keyToShowWikipediaFor | wikidata | Use the wikidata entry from this key to show #### 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 - A small map showing the selected feature. Note that no styling is applied, wrap this in a div + A small map showing the selected feature. name | default | description ------ | --------- | ------------- @@ -53,7 +86,22 @@ idKey | id | (Matches all resting arguments) This argument should be the key of #### 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 + + 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 + +name | default | description +------ | --------- | ------------- +side | _undefined_ | The side to show, either `left` or `right` + +#### Example usage + + `{sided_minimap(left)}` + + ### reviews Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten @@ -61,11 +109,13 @@ idKey | id | (Matches all resting arguments) This argument should be the key of name | default | description ------ | --------- | ------------- subjectKey | name | The key to use to determine the subject. If specified, the subject will be tags[subjectKey] -fallback | undefined | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value +fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value #### Example usage - `{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used + `{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used + + ### opening_hours_table Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'. @@ -73,63 +123,77 @@ fallback | undefined | The identifier to use, if tags[subjectKey] as spec name | default | description ------ | --------- | ------------- key | opening_hours | The tagkey from which the table is constructed. +prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__ +postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__ #### Example usage - `{opening_hours_table(opening_hours)}` + A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}` + + ### live Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)} name | default | description ------ | --------- | ------------- -Url | undefined | The URL to load -Shorthands | undefined | A list of shorthands, of the format 'shorthandname:path.path.path'. separated by ; -path | undefined | The path (or shorthand) that should be returned +Url | _undefined_ | The URL to load +Shorthands | _undefined_ | A list of shorthands, of the format 'shorthandname:path.path.path'. separated by ; +path | _undefined_ | The path (or shorthand) that should be returned #### Example usage - {live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)} + {live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)} + + ### histogram Create a histogram for a list of given values, read from the properties. name | default | description ------ | --------- | ------------- -key | undefined | The key to be read and to generate a histogram from -title | | The text to put above the given values column -countHeader | | 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` +key | _undefined_ | The key to be read and to generate a histogram from +title | _empty string_ | The text to put above the given values column +countHeader | _empty string_ | The text to put above the counts +colors* | _undefined_ | (Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33` #### Example usage - `{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram + `{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram + + ### share_link Creates a link that (attempts to) open the native 'share'-screen name | default | description ------ | --------- | ------------- -url | undefined | The url to share (default: current URL) +url | _undefined_ | The url to share (default: current URL) #### Example usage - {share_link()} to share the current page, {share_link()} to share the given url + {share_link()} to share the current page, {share_link()} to share the given url + + ### canonical Converts a short, canonical value into the long, translated text 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 - {canonical(length)} will give 42 metre (in french) + {canonical(length)} will give 42 metre (in french) + + ### import_button This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes. +#### Importing a dataset into OpenStreetMap: requirements + If you want to import a dataset, make sure that: 1. The dataset to import has a suitable license @@ -138,36 +202,92 @@ If you want to import a dataset, make sure that: There are also some technicalities in your theme to keep in mind: -1. The new point will be added and will flow through the program as any other new point as if it came from OSM. +1. The new feature will be added and will flow through the program as any other new point as if it came from OSM. This means that there should be a layer which will match the new tags and which will display it. -2. The original point from your geojson layer will gain the tag '_imported=yes'. +2. The original feature from your geojson layer will gain the tag '_imported=yes'. This should be used to change the appearance or even to hide it (eg by changing the icon size to zero) 3. There should be a way for the theme to detect previously imported points, even after reloading. - A reference number to the original dataset is an excellen way to do this + A reference number to the original dataset is an excellent way to do this +4. When importing ways, the theme creator is also responsible of avoiding overlapping ways. + +#### Disabled in unofficial themes + +The import button can be tested in an unofficial theme by adding `test=true` or `backend=osm-test` as [URL-paramter](URL_Parameters.md). +The import button will show up then. If in testmode, you can read the changeset-XML directly in the web console. +In the case that MapComplete is pointed to the testing grounds, the edit will be made on https://master.apis.dev.openstreetmap.org + + +#### Specifying which tags to copy or add + +The first argument of the import button takes a `;`-seperated list of tags to add. + +These can either be a tag to add, such as `amenity=fast_food` or can use a substitution, e.g. `addr:housenumber=$number`. +This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. + +If a value to substitute is undefined, empty string will be used instead. + +This supports multiple values, e.g. `ref=$source:geometry:type/$source:geometry:ref` + +Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with `[a-zA-Z0-9_:]*`). Sadly, delimiting with `{}` as these already mark the boundaries of the special rendering... + +Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript) + + name | default | description ------ | --------- | ------------- -tags | undefined | Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber=$number`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags) +tags | _undefined_ | The tags to add onto the new object - see specification above text | Import this data into OpenStreetMap | The text to show on the button icon | ./assets/svg/addSmall.svg | A nice icon to show in the button minzoom | 18 | How far the contributor must zoom in before being able to import the point #### Example usage - `{import_button(,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18)}` + `{import_button(,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18)}` + + ### 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 name | default | description ------ | --------- | ------------- -feature_ids | undefined | A JSOn-serialized list of IDs of features to apply the tagging on -keys | undefined | One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features. -text | undefined | The text to show on the button -autoapply | undefined | A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown -overwrite | undefined | If set to 'true', the tags on the other objects will always be overwritten. The default behaviour will be to only change the tags on other objects if they are either undefined or had the same value before the change +feature_ids | _undefined_ | A JSOn-serialized list of IDs of features to apply the tagging on +keys | _undefined_ | One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features. +text | _undefined_ | The text to show on the button +autoapply | _undefined_ | A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown +overwrite | _undefined_ | If set to 'true', the tags on the other objects will always be overwritten. The default behaviour will be to only change the tags on other objects if they are either undefined or had the same value before the change #### Example usage - {multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)} Generated from UI/SpecialVisualisations.ts \ No newline at end of file + {multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)} + + +### tag_apply + + Shows a big button; clicking this button will apply certain tags onto the feature. + +The first argument takes a specification of which tags to add. +These can either be a tag to add, such as `amenity=fast_food` or can use a substitution, e.g. `addr:housenumber=$number`. +This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. + +If a value to substitute is undefined, empty string will be used instead. + +This supports multiple values, e.g. `ref=$source:geometry:type/$source:geometry:ref` + +Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with `[a-zA-Z0-9_:]*`). Sadly, delimiting with `{}` as these already mark the boundaries of the special rendering... + +Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript) + + +name | default | description +------ | --------- | ------------- +tags_to_apply | _undefined_ | A specification of the tags to apply +message | _undefined_ | The text to show to the contributor +image | _undefined_ | An image to show to the contributor on the button +id_of_object_to_apply_this_one | _undefined_ | If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element + +#### Example usage + + `{tag_apply(survey_date:=$_now:date, Surveyed today!)}` Generated from UI/SpecialVisualisations.ts \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_charging_stations.json b/Docs/TagInfo/mapcomplete_charging_stations.json index 6cd7b7772..d1c40a915 100644 --- a/Docs/TagInfo/mapcomplete_charging_stations.json +++ b/Docs/TagInfo/mapcomplete_charging_stations.json @@ -92,7 +92,7 @@ }, { "key": "access", - "description": "Layer 'Charging stations' shows access=customers with a fixed text, namely 'Only customers of the place this station belongs to can use this charging station
E.g. a charging station operated by hotel which is only usable by their guests ' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "description": "Layer 'Charging stations' shows access=customers with a fixed text, namely 'Only customers of the place this station belongs to can use this charging station
E.g. a charging station operated by hotel which is only usable by their guests' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", "value": "customers" }, { @@ -252,632 +252,163 @@ "key": "socket:schuko", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:schuko' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:schuko:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:schuko:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:schuko:voltage", - "description": "Layer 'Charging stations' shows socket:socket:schuko:voltage=230 V with a fixed text, namely '
Schuko wall plug without ground pin (CEE7/4 type F)
outputs 230 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "230 V" - }, - { - "key": "socket:schuko:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:schuko:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:schuko:current", - "description": "Layer 'Charging stations' shows socket:socket:schuko:current=16 A with a fixed text, namely '
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 16 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "16 A" - }, - { - "key": "socket:schuko:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:schuko:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:schuko:output", - "description": "Layer 'Charging stations' shows socket:socket:schuko:output=3.6 kw with a fixed text, namely '
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 3.6 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "3.6 kw" - }, { "key": "socket:typee", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:typee' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:typee:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:typee:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:typee:voltage", - "description": "Layer 'Charging stations' shows socket:socket:typee:voltage=230 V with a fixed text, namely '
European wall plug with ground pin (CEE7/4 type E)
outputs 230 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "230 V" - }, - { - "key": "socket:typee:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:typee:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:typee:current", - "description": "Layer 'Charging stations' shows socket:socket:typee:current=16 A with a fixed text, namely '
European wall plug with ground pin (CEE7/4 type E)
outputs at most 16 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "16 A" - }, - { - "key": "socket:typee:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:typee:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:typee:output", - "description": "Layer 'Charging stations' shows socket:socket:typee:output=3 kw with a fixed text, namely '
European wall plug with ground pin (CEE7/4 type E)
outputs at most 3 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "3 kw" - }, - { - "key": "socket:socket:typee:output", - "description": "Layer 'Charging stations' shows socket:socket:typee:output=22 kw with a fixed text, namely '
European wall plug with ground pin (CEE7/4 type E)
outputs at most 22 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "22 kw" - }, { "key": "socket:chademo", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:chademo' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:chademo:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:chademo:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:chademo:voltage", - "description": "Layer 'Charging stations' shows socket:socket:chademo:voltage=500 V with a fixed text, namely '
Chademo
outputs 500 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "500 V" - }, - { - "key": "socket:chademo:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:chademo:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:chademo:current", - "description": "Layer 'Charging stations' shows socket:socket:chademo:current=120 A with a fixed text, namely '
Chademo
outputs at most 120 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "120 A" - }, - { - "key": "socket:chademo:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:chademo:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:chademo:output", - "description": "Layer 'Charging stations' shows socket:socket:chademo:output=50 kw with a fixed text, namely '
Chademo
outputs at most 50 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "50 kw" - }, { "key": "socket:type1_cable", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_cable' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type1_cable:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_cable:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_cable:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1_cable:voltage=200 V with a fixed text, namely '
Type 1 with cable (J1772)
outputs 200 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "200 V" - }, - { - "key": "socket:socket:type1_cable:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1_cable:voltage=240 V with a fixed text, namely '
Type 1 with cable (J1772)
outputs 240 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "240 V" - }, - { - "key": "socket:type1_cable:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_cable:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_cable:current", - "description": "Layer 'Charging stations' shows socket:socket:type1_cable:current=32 A with a fixed text, namely '
Type 1 with cable (J1772)
outputs at most 32 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "32 A" - }, - { - "key": "socket:type1_cable:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_cable:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_cable:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_cable:output=3.7 kw with a fixed text, namely '
Type 1 with cable (J1772)
outputs at most 3.7 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "3.7 kw" - }, - { - "key": "socket:socket:type1_cable:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_cable:output=7 kw with a fixed text, namely '
Type 1 with cable (J1772)
outputs at most 7 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "7 kw" - }, { "key": "socket:type1", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type1:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1:voltage=200 V with a fixed text, namely '
Type 1 without cable (J1772)
outputs 200 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "200 V" - }, - { - "key": "socket:socket:type1:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1:voltage=240 V with a fixed text, namely '
Type 1 without cable (J1772)
outputs 240 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "240 V" - }, - { - "key": "socket:type1:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1:current", - "description": "Layer 'Charging stations' shows socket:socket:type1:current=32 A with a fixed text, namely '
Type 1 without cable (J1772)
outputs at most 32 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "32 A" - }, - { - "key": "socket:type1:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1:output", - "description": "Layer 'Charging stations' shows socket:socket:type1:output=3.7 kw with a fixed text, namely '
Type 1 without cable (J1772)
outputs at most 3.7 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "3.7 kw" - }, - { - "key": "socket:socket:type1:output", - "description": "Layer 'Charging stations' shows socket:socket:type1:output=6.6 kw with a fixed text, namely '
Type 1 without cable (J1772)
outputs at most 6.6 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "6.6 kw" - }, - { - "key": "socket:socket:type1:output", - "description": "Layer 'Charging stations' shows socket:socket:type1:output=7 kw with a fixed text, namely '
Type 1 without cable (J1772)
outputs at most 7 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "7 kw" - }, - { - "key": "socket:socket:type1:output", - "description": "Layer 'Charging stations' shows socket:socket:type1:output=7.2 kw with a fixed text, namely '
Type 1 without cable (J1772)
outputs at most 7.2 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "7.2 kw" - }, { "key": "socket:type1_combo", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_combo' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type1_combo:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_combo:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_combo:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:voltage=400 V with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs 400 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "400 V" - }, - { - "key": "socket:socket:type1_combo:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:voltage=1000 V with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs 1000 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "1000 V" - }, - { - "key": "socket:type1_combo:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_combo:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_combo:current", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:current=50 A with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "50 A" - }, - { - "key": "socket:socket:type1_combo:current", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:current=125 A with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 125 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "125 A" - }, - { - "key": "socket:type1_combo:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type1_combo:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type1_combo:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:output=50 kw with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "50 kw" - }, - { - "key": "socket:socket:type1_combo:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:output=62.5 kw with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 62.5 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "62.5 kw" - }, - { - "key": "socket:socket:type1_combo:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:output=150 kw with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 150 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "150 kw" - }, - { - "key": "socket:socket:type1_combo:output", - "description": "Layer 'Charging stations' shows socket:socket:type1_combo:output=350 kw with a fixed text, namely '
Type 1 CCS (aka Type 1 Combo)
outputs at most 350 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "350 kw" - }, { "key": "socket:tesla_supercharger", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:tesla_supercharger:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:voltage=480 V with a fixed text, namely '
Tesla Supercharger
outputs 480 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "480 V" - }, - { - "key": "socket:tesla_supercharger:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:current=125 A with a fixed text, namely '
Tesla Supercharger
outputs at most 125 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "125 A" - }, - { - "key": "socket:socket:tesla_supercharger:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:current=350 A with a fixed text, namely '
Tesla Supercharger
outputs at most 350 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "350 A" - }, - { - "key": "socket:tesla_supercharger:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:output=120 kw with a fixed text, namely '
Tesla Supercharger
outputs at most 120 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "120 kw" - }, - { - "key": "socket:socket:tesla_supercharger:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:output=150 kw with a fixed text, namely '
Tesla Supercharger
outputs at most 150 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "150 kw" - }, - { - "key": "socket:socket:tesla_supercharger:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger:output=250 kw with a fixed text, namely '
Tesla Supercharger
outputs at most 250 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "250 kw" - }, { "key": "socket:type2", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type2:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2:voltage=230 V with a fixed text, namely '
Type 2 (mennekes)
outputs 230 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "230 V" - }, - { - "key": "socket:socket:type2:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2:voltage=400 V with a fixed text, namely '
Type 2 (mennekes)
outputs 400 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "400 V" - }, - { - "key": "socket:type2:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2:current", - "description": "Layer 'Charging stations' shows socket:socket:type2:current=16 A with a fixed text, namely '
Type 2 (mennekes)
outputs at most 16 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "16 A" - }, - { - "key": "socket:socket:type2:current", - "description": "Layer 'Charging stations' shows socket:socket:type2:current=32 A with a fixed text, namely '
Type 2 (mennekes)
outputs at most 32 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "32 A" - }, - { - "key": "socket:type2:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2:output", - "description": "Layer 'Charging stations' shows socket:socket:type2:output=11 kw with a fixed text, namely '
Type 2 (mennekes)
outputs at most 11 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "11 kw" - }, - { - "key": "socket:socket:type2:output", - "description": "Layer 'Charging stations' shows socket:socket:type2:output=22 kw with a fixed text, namely '
Type 2 (mennekes)
outputs at most 22 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "22 kw" - }, { "key": "socket:type2_combo", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_combo' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type2_combo:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_combo:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_combo:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2_combo:voltage=500 V with a fixed text, namely '
Type 2 CCS (mennekes)
outputs 500 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "500 V" - }, - { - "key": "socket:socket:type2_combo:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2_combo:voltage=920 V with a fixed text, namely '
Type 2 CCS (mennekes)
outputs 920 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "920 V" - }, - { - "key": "socket:type2_combo:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_combo:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_combo:current", - "description": "Layer 'Charging stations' shows socket:socket:type2_combo:current=125 A with a fixed text, namely '
Type 2 CCS (mennekes)
outputs at most 125 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "125 A" - }, - { - "key": "socket:socket:type2_combo:current", - "description": "Layer 'Charging stations' shows socket:socket:type2_combo:current=350 A with a fixed text, namely '
Type 2 CCS (mennekes)
outputs at most 350 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "350 A" - }, - { - "key": "socket:type2_combo:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_combo:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_combo:output", - "description": "Layer 'Charging stations' shows socket:socket:type2_combo:output=50 kw with a fixed text, namely '
Type 2 CCS (mennekes)
outputs at most 50 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "50 kw" - }, { "key": "socket:type2_cable", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_cable' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:type2_cable:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_cable:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_cable:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:voltage=230 V with a fixed text, namely '
Type 2 with cable (mennekes)
outputs 230 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "230 V" - }, - { - "key": "socket:socket:type2_cable:voltage", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:voltage=400 V with a fixed text, namely '
Type 2 with cable (mennekes)
outputs 400 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "400 V" - }, - { - "key": "socket:type2_cable:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_cable:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_cable:current", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:current=16 A with a fixed text, namely '
Type 2 with cable (mennekes)
outputs at most 16 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "16 A" - }, - { - "key": "socket:socket:type2_cable:current", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:current=32 A with a fixed text, namely '
Type 2 with cable (mennekes)
outputs at most 32 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "32 A" - }, - { - "key": "socket:type2_cable:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:type2_cable:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:type2_cable:output", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:output=11 kw with a fixed text, namely '
Type 2 with cable (mennekes)
outputs at most 11 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "11 kw" - }, - { - "key": "socket:socket:type2_cable:output", - "description": "Layer 'Charging stations' shows socket:socket:type2_cable:output=22 kw with a fixed text, namely '
Type 2 with cable (mennekes)
outputs at most 22 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "22 kw" - }, { "key": "socket:tesla_supercharger_ccs", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger_ccs' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:tesla_supercharger_ccs:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger_ccs:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger_ccs:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger_ccs:voltage=500 V with a fixed text, namely '
Tesla Supercharger CCS (a branded type2_css)
outputs 500 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "500 V" - }, - { - "key": "socket:socket:tesla_supercharger_ccs:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger_ccs:voltage=920 V with a fixed text, namely '
Tesla Supercharger CCS (a branded type2_css)
outputs 920 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "920 V" - }, - { - "key": "socket:tesla_supercharger_ccs:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger_ccs:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger_ccs:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger_ccs:current=125 A with a fixed text, namely '
Tesla Supercharger CCS (a branded type2_css)
outputs at most 125 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "125 A" - }, - { - "key": "socket:socket:tesla_supercharger_ccs:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger_ccs:current=350 A with a fixed text, namely '
Tesla Supercharger CCS (a branded type2_css)
outputs at most 350 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "350 A" - }, - { - "key": "socket:tesla_supercharger_ccs:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_supercharger_ccs:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_supercharger_ccs:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_supercharger_ccs:output=50 kw with a fixed text, namely '
Tesla Supercharger CCS (a branded type2_css)
outputs at most 50 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "50 kw" - }, { "key": "socket:tesla_destination", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:tesla_destination:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:voltage=480 V with a fixed text, namely '
Tesla Supercharger (destination)
outputs 480 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "480 V" - }, - { - "key": "socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:current=125 A with a fixed text, namely '
Tesla Supercharger (destination)
outputs at most 125 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "125 A" - }, - { - "key": "socket:socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:current=350 A with a fixed text, namely '
Tesla Supercharger (destination)
outputs at most 350 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "350 A" - }, - { - "key": "socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:output=120 kw with a fixed text, namely '
Tesla Supercharger (destination)
outputs at most 120 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "120 kw" - }, - { - "key": "socket:socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:output=150 kw with a fixed text, namely '
Tesla Supercharger (destination)
outputs at most 150 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "150 kw" - }, - { - "key": "socket:socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:output=250 kw with a fixed text, namely '
Tesla Supercharger (destination)
outputs at most 250 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "250 kw" - }, { "key": "socket:tesla_destination", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:tesla_destination:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:voltage=230 V with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 230 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "230 V" - }, - { - "key": "socket:socket:tesla_destination:voltage", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:voltage=400 V with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 400 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "400 V" - }, - { - "key": "socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:current=16 A with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 16 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "16 A" - }, - { - "key": "socket:socket:tesla_destination:current", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:current=32 A with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 32 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "32 A" - }, - { - "key": "socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:tesla_destination:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:output=11 kw with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 11 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "11 kw" - }, - { - "key": "socket:socket:tesla_destination:output", - "description": "Layer 'Charging stations' shows socket:socket:tesla_destination:output=22 kw with a fixed text, namely '
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 22 kw' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "22 kw" - }, { "key": "socket:USB-A", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:USB-A' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:USB-A:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:USB-A:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:USB-A:voltage", - "description": "Layer 'Charging stations' shows socket:socket:USB-A:voltage=5 V with a fixed text, namely '
USB to charge phones and small electronics
outputs 5 volt' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "5 V" - }, - { - "key": "socket:USB-A:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:USB-A:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:USB-A:current", - "description": "Layer 'Charging stations' shows socket:socket:USB-A:current=1 A with a fixed text, namely '
USB to charge phones and small electronics
outputs at most 1 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "1 A" - }, - { - "key": "socket:socket:USB-A:current", - "description": "Layer 'Charging stations' shows socket:socket:USB-A:current=2 A with a fixed text, namely '
USB to charge phones and small electronics
outputs at most 2 A' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "2 A" - }, - { - "key": "socket:USB-A:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:USB-A:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:socket:USB-A:output", - "description": "Layer 'Charging stations' shows socket:socket:USB-A:output=5w with a fixed text, namely '
USB to charge phones and small electronics
outputs at most 5w' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "5w" - }, - { - "key": "socket:socket:USB-A:output", - "description": "Layer 'Charging stations' shows socket:socket:USB-A:output=10w with a fixed text, namely '
USB to charge phones and small electronics
outputs at most 10w' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "10w" - }, { "key": "socket:bosch_3pin", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_3pin' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "socket:bosch_3pin:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_3pin:voltage' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:bosch_3pin:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_3pin:current' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "socket:bosch_3pin:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_3pin:output' (in the MapComplete.osm.be theme 'Charging stations')" - }, { "key": "socket:bosch_5pin", "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_5pin' (in the MapComplete.osm.be theme 'Charging stations')" }, { - "key": "socket:bosch_5pin:voltage", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_5pin:voltage' (in the MapComplete.osm.be theme 'Charging stations')" + "key": "opening_hours", + "description": "Layer 'Charging stations' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Charging stations')" }, { - "key": "socket:bosch_5pin:current", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_5pin:current' (in the MapComplete.osm.be theme 'Charging stations')" + "key": "opening_hours", + "description": "Layer 'Charging stations' shows opening_hours=24/7 with a fixed text, namely '24/7 opened (including holidays)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "24/7" }, { - "key": "socket:bosch_5pin:output", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'socket:bosch_5pin:output' (in the MapComplete.osm.be theme 'Charging stations')" + "key": "fee", + "description": "Layer 'Charging stations' shows fee=no with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Charging stations')", + "value": "no" + }, + { + "key": "fee", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=yes with a fixed text, namely 'Free to use (without authenticating)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "no" + }, + { + "key": "fee:conditional", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=yes with a fixed text, namely 'Free to use (without authenticating)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key fee:conditional.", + "value": "" + }, + { + "key": "charge", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=yes with a fixed text, namely 'Free to use (without authenticating)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key charge.", + "value": "" + }, + { + "key": "authentication:none", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=yes with a fixed text, namely 'Free to use (without authenticating)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "fee", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=no with a fixed text, namely 'Free to use, but one has to authenticate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "no" + }, + { + "key": "fee:conditional", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=no with a fixed text, namely 'Free to use, but one has to authenticate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key fee:conditional.", + "value": "" + }, + { + "key": "charge", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=no with a fixed text, namely 'Free to use, but one has to authenticate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key charge.", + "value": "" + }, + { + "key": "authentication:none", + "description": "Layer 'Charging stations' shows fee=no&fee:conditional=&charge=&authentication:none=no with a fixed text, namely 'Free to use, but one has to authenticate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "no" + }, + { + "key": "fee", + "description": "Layer 'Charging stations' shows fee=yes&fee:conditional=no @ customers with a fixed text, namely 'Paid use, but free for customers of the hotel/pub/hospital/... who operates the charging station' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "fee:conditional", + "description": "Layer 'Charging stations' shows fee=yes&fee:conditional=no @ customers with a fixed text, namely 'Paid use, but free for customers of the hotel/pub/hospital/... who operates the charging station' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "no @ customers" + }, + { + "key": "fee", + "description": "Layer 'Charging stations' shows fee=yes&fee:conditional= with a fixed text, namely 'Paid use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "fee:conditional", + "description": "Layer 'Charging stations' shows fee=yes&fee:conditional= with a fixed text, namely 'Paid use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key fee:conditional.", + "value": "" + }, + { + "key": "charge", + "description": "Layer 'Charging stations' shows and asks freeform values for key 'charge' (in the MapComplete.osm.be theme 'Charging stations')" + }, + { + "key": "payment:cash", + "description": "Layer 'Charging stations' 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 'Charging stations')", + "value": "yes" + }, + { + "key": "payment:cards", + "description": "Layer 'Charging stations' 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 'Charging stations')", + "value": "yes" + }, + { + "key": "payment:app", + "description": "Layer 'Charging stations' 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 'Charging stations')", + "value": "yes" + }, + { + "key": "payment:membership_card", + "description": "Layer 'Charging stations' 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 'Charging stations')", + "value": "yes" }, { "key": "authentication:membership_card", @@ -916,56 +447,13 @@ }, { "key": "authentication:none", - "description": "Layer 'Charging stations' shows authentication:none=yes with a fixed text, namely 'No authentication is needed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "description": "Layer 'Charging stations' shows authentication:none=yes with a fixed text, namely 'Charging here is (also) possible without authentication' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", "value": "yes" }, { "key": "authentication:phone_call:number", "description": "Layer 'Charging stations' shows and asks freeform values for key 'authentication:phone_call:number' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "opening_hours", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "opening_hours", - "description": "Layer 'Charging stations' shows opening_hours=24/7 with a fixed text, namely '24/7 opened (including holidays)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "24/7" - }, - { - "key": "charge", - "description": "Layer 'Charging stations' shows and asks freeform values for key 'charge' (in the MapComplete.osm.be theme 'Charging stations')" - }, - { - "key": "fee", - "description": "Layer 'Charging stations' shows fee=no&charge= with a fixed text, namely 'Free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "no" - }, - { - "key": "charge", - "description": "Layer 'Charging stations' shows fee=no&charge= with a fixed text, namely 'Free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key charge.", - "value": "" - }, - { - "key": "payment:cash", - "description": "Layer 'Charging stations' 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 'Charging stations')", - "value": "yes" - }, - { - "key": "payment:cards", - "description": "Layer 'Charging stations' 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 'Charging stations')", - "value": "yes" - }, - { - "key": "payment:app", - "description": "Layer 'Charging stations' 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 'Charging stations')", - "value": "yes" - }, - { - "key": "payment:membership_card", - "description": "Layer 'Charging stations' 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 'Charging stations')", - "value": "yes" - }, { "key": "maxstay", "description": "Layer 'Charging stations' shows and asks freeform values for key 'maxstay' (in the MapComplete.osm.be theme 'Charging stations')" @@ -1053,51 +541,131 @@ "key": "ref", "description": "Layer 'Charging stations' shows and asks freeform values for key 'ref' (in the MapComplete.osm.be theme 'Charging stations')" }, - { - "key": "operational_status", - "description": "Layer 'Charging stations' shows operational_status=broken 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" - }, { "key": "planned:amenity", - "description": "Layer 'Charging stations' shows planned:amenity=charging_station&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "charging_station" - }, - { - "key": "amenity", - "description": "Layer 'Charging stations' shows planned:amenity=charging_station&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=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", "value": "" }, { "key": "construction:amenity", - "description": "Layer 'Charging stations' shows construction:amenity=charging_station&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "charging_station" - }, - { - "key": "amenity", - "description": "Layer 'Charging stations' shows construction:amenity=charging_station&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=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", "value": "" }, { "key": "disused:amenity", - "description": "Layer 'Charging stations' shows disused:amenity=charging_station&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')", - "value": "charging_station" + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "broken" }, { "key": "amenity", - "description": "Layer 'Charging stations' shows disused:amenity=charging_station&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=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", "value": "" }, { "key": "amenity", - "description": "Layer 'Charging stations' shows amenity=charging_station&operational_status= 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')", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", + "value": "" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", + "value": "" + }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", + "value": "" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", "value": "charging_station" }, { "key": "operational_status", - "description": "Layer 'Charging stations' shows amenity=charging_station&operational_status= 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=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", "value": "" }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", + "value": "" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", + "value": "" + }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, { "key": "parking:fee", "description": "Layer 'Charging stations' shows parking:fee=no with a fixed text, namely 'No additional parking cost while charging' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", diff --git a/Docs/TagInfo/mapcomplete_climbing.json b/Docs/TagInfo/mapcomplete_climbing.json index d0505237c..75142f1ce 100644 --- a/Docs/TagInfo/mapcomplete_climbing.json +++ b/Docs/TagInfo/mapcomplete_climbing.json @@ -194,6 +194,26 @@ "key": "wikipedia", "description": "The layer 'Climbing gyms allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "name", + "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "website", + "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'website' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "phone", + "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'phone' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "email", + "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'email' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "opening_hours", + "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, { "key": "url", "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'url' (in the MapComplete.osm.be theme 'Open Climbing Map')" @@ -314,26 +334,6 @@ "key": "climbing:speed", "description": "Layer 'Climbing gyms' shows climbing:speed~^..*$ with a fixed text, namely 'There are {climbing:speed} speed climbing walls' (in the MapComplete.osm.be theme 'Open Climbing Map')" }, - { - "key": "name", - "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "website", - "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'website' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "phone", - "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'phone' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "email", - "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'email' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "opening_hours", - "description": "Layer 'Climbing gyms' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, { "key": "climbing", "description": "The MapComplete theme Open Climbing Map has a layer Climbing routes showing features with this tag", @@ -355,6 +355,46 @@ "key": "wikipedia", "description": "The layer 'Climbing routes allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "name", + "description": "Layer 'Climbing routes' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "noname", + "description": "Layer 'Climbing routes' shows noname=yes&name= with a fixed text, namely 'This climbing route doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "yes" + }, + { + "key": "name", + "description": "Layer 'Climbing routes' shows noname=yes&name= with a fixed text, namely 'This climbing route doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map') Picking this answer will delete the key name.", + "value": "" + }, + { + "key": "climbing:length", + "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:length' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "climbing:grade:french", + "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:grade:french' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "climbing:bolts", + "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:bolts' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "climbing:bolted", + "description": "Layer 'Climbing routes' shows climbing:bolted=no with a fixed text, namely 'This route is not bolted' (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "no" + }, + { + "key": "climbing:bolted", + "description": "Layer 'Climbing routes' shows climbing:bolted=no&climbing:bolts= with a fixed text, namely 'This route is not bolted' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "no&climbing:bolts=" + }, + { + "key": "description", + "description": "Layer 'Climbing routes' shows and asks freeform values for key 'description' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, { "key": "url", "description": "Layer 'Climbing routes' shows and asks freeform values for key 'url' (in the MapComplete.osm.be theme 'Open Climbing Map')" @@ -475,46 +515,6 @@ "key": "climbing:speed", "description": "Layer 'Climbing routes' shows climbing:speed~^..*$ with a fixed text, namely 'There are {climbing:speed} speed climbing walls' (in the MapComplete.osm.be theme 'Open Climbing Map')" }, - { - "key": "name", - "description": "Layer 'Climbing routes' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "noname", - "description": "Layer 'Climbing routes' shows noname=yes&name= with a fixed text, namely 'This climbing route doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "yes" - }, - { - "key": "name", - "description": "Layer 'Climbing routes' shows noname=yes&name= with a fixed text, namely 'This climbing route doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map') Picking this answer will delete the key name.", - "value": "" - }, - { - "key": "climbing:length", - "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:length' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "climbing:grade:french", - "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:grade:french' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "climbing:bolts", - "description": "Layer 'Climbing routes' shows and asks freeform values for key 'climbing:bolts' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "climbing:bolted", - "description": "Layer 'Climbing routes' shows climbing:bolted=no with a fixed text, namely 'This route is not bolted' (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "no" - }, - { - "key": "climbing:bolted", - "description": "Layer 'Climbing routes' shows climbing:bolted=no&climbing:bolts= with a fixed text, namely 'This route is not bolted' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "no&climbing:bolts=" - }, - { - "key": "description", - "description": "Layer 'Climbing routes' shows and asks freeform values for key 'description' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, { "key": "sport", "description": "The MapComplete theme Open Climbing Map has a layer Climbing opportunities showing features with this tag", @@ -536,6 +536,44 @@ "key": "wikipedia", "description": "The layer 'Climbing opportunities allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, + { + "key": "name", + "description": "Layer 'Climbing opportunities' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "noname", + "description": "Layer 'Climbing opportunities' shows noname=yes&name= with a fixed text, namely 'This climbing opportunity doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "yes" + }, + { + "key": "name", + "description": "Layer 'Climbing opportunities' shows noname=yes&name= with a fixed text, namely 'This climbing opportunity doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map') Picking this answer will delete the key name.", + "value": "" + }, + { + "key": "climbing", + "description": "Layer 'Climbing opportunities' shows climbing=boulder with a fixed text, namely 'A climbing boulder - a single rock or cliff with one or a few climbing routes which can be climbed safely without rope' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "boulder" + }, + { + "key": "climbing", + "description": "Layer 'Climbing opportunities' shows climbing=crag with a fixed text, namely 'A climbing crag - a single rock or cliff with at least a few climbing routes' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "crag" + }, + { + "key": "climbing", + "description": "Layer 'Climbing opportunities' shows climbing=area with a fixed text, namely 'A climbing area with one or more climbing crags and/or boulders' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "area" + }, + { + "key": "rock", + "description": "Layer 'Climbing opportunities' shows and asks freeform values for key 'rock' (in the MapComplete.osm.be theme 'Open Climbing Map')" + }, + { + "key": "rock", + "description": "Layer 'Climbing opportunities' shows rock=limestone with a fixed text, namely 'Limestone' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", + "value": "limestone" + }, { "key": "url", "description": "Layer 'Climbing opportunities' shows and asks freeform values for key 'url' (in the MapComplete.osm.be theme 'Open Climbing Map')" @@ -656,44 +694,6 @@ "key": "climbing:speed", "description": "Layer 'Climbing opportunities' shows climbing:speed~^..*$ with a fixed text, namely 'There are {climbing:speed} speed climbing walls' (in the MapComplete.osm.be theme 'Open Climbing Map')" }, - { - "key": "name", - "description": "Layer 'Climbing opportunities' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "noname", - "description": "Layer 'Climbing opportunities' shows noname=yes&name= with a fixed text, namely 'This climbing opportunity doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "yes" - }, - { - "key": "name", - "description": "Layer 'Climbing opportunities' shows noname=yes&name= with a fixed text, namely 'This climbing opportunity doesn't have a name' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map') Picking this answer will delete the key name.", - "value": "" - }, - { - "key": "climbing", - "description": "Layer 'Climbing opportunities' shows climbing=boulder with a fixed text, namely 'A climbing boulder - a single rock or cliff with one or a few climbing routes which can be climbed safely without rope' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "boulder" - }, - { - "key": "climbing", - "description": "Layer 'Climbing opportunities' shows climbing=crag with a fixed text, namely 'A climbing crag - a single rock or cliff with at least a few climbing routes' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "crag" - }, - { - "key": "climbing", - "description": "Layer 'Climbing opportunities' shows climbing=area with a fixed text, namely 'A climbing area with one or more climbing crags and/or boulders' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "area" - }, - { - "key": "rock", - "description": "Layer 'Climbing opportunities' shows and asks freeform values for key 'rock' (in the MapComplete.osm.be theme 'Open Climbing Map')" - }, - { - "key": "rock", - "description": "Layer 'Climbing opportunities' shows rock=limestone with a fixed text, namely 'Limestone' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Climbing Map')", - "value": "limestone" - }, { "key": "leisure", "description": "The MapComplete theme Open Climbing Map has a layer Climbing opportunities? showing features with this tag", diff --git a/Docs/TagInfo/mapcomplete_cyclofix.json b/Docs/TagInfo/mapcomplete_cyclofix.json index 9138e75b4..f15af7e5c 100644 --- a/Docs/TagInfo/mapcomplete_cyclofix.json +++ b/Docs/TagInfo/mapcomplete_cyclofix.json @@ -116,25 +116,6 @@ "key": "opening_hours", "description": "Layer 'Bike cafe' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bike cafe' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike cafe' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike cafe' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike cafe' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "shop", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Bike repair/shop showing features with this tag", @@ -417,25 +398,6 @@ "key": "description", "description": "Layer 'Bicycle library' shows and asks freeform values for key 'description' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bicycle library' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle library' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle library' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle library' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "amenity", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Bike stations (repair, pump or both) showing features with this tag", @@ -616,25 +578,6 @@ "description": "Layer 'Bike stations (repair, pump or both)' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", "value": "1" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bike stations (repair, pump or both)' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike stations (repair, pump or both)' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike stations (repair, pump or both)' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike stations (repair, pump or both)' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "amenity", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Bicycle tube vending machine showing features with this tag", @@ -751,25 +694,6 @@ "description": "Layer 'Bicycle tube vending machine' shows vending:bicycle_lock=yes with a fixed text, namely 'Bicycle locks are sold here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", "value": "yes" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bicycle tube vending machine' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle tube vending machine' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle tube vending machine' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bicycle tube vending machine' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "amenity", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Drinking water showing features with this tag", @@ -820,25 +744,6 @@ "description": "Layer 'Drinking water' shows bottle=no with a fixed text, namely 'Water bottles may not fit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", "value": "no" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Drinking water' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "theme", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Bike related object showing features with this tag", @@ -920,25 +825,6 @@ "key": "opening_hours", "description": "Layer 'Bike related object' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bike related object' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike related object' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike related object' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike related object' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" - }, { "key": "service:bicycle:cleaning", "description": "The MapComplete theme Cyclofix - an open map for cyclists has a layer Bike cleaning service showing features with this tag", @@ -1149,25 +1035,6 @@ { "key": "capacity:cargo_bike", "description": "Layer 'Bike parking' shows and asks freeform values for key 'capacity:cargo_bike' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Bike parking' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike parking' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike parking' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Bike parking' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclofix - an open map for cyclists')", - "value": "yes" } ] } \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_drinking_water.json b/Docs/TagInfo/mapcomplete_drinking_water.json index 2c687ac59..2db459901 100644 --- a/Docs/TagInfo/mapcomplete_drinking_water.json +++ b/Docs/TagInfo/mapcomplete_drinking_water.json @@ -59,25 +59,6 @@ "key": "bottle", "description": "Layer 'Drinking water' shows bottle=no with a fixed text, namely 'Water bottles may not fit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Drinking Water')", "value": "no" - }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Drinking water' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'Drinking Water')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Drinking Water')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'Drinking Water')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Drinking Water')", - "value": "yes" } ] } \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_etymology.json b/Docs/TagInfo/mapcomplete_etymology.json new file mode 100644 index 000000000..ae6229db9 --- /dev/null +++ b/Docs/TagInfo/mapcomplete_etymology.json @@ -0,0 +1,206 @@ +{ + "data_format": 1, + "project": { + "name": "MapComplete Open Etymology Map", + "description": "What is the origin of a toponym?", + "project_url": "https://mapcomplete.osm.be/etymology", + "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", + "icon_url": "https://mapcomplete.osm.be/assets/layers/etymology/logo.svg", + "contact_name": "Pieter Vander Vennet, ", + "contact_email": "pietervdvn@posteo.net" + }, + "tags": [ + { + "key": "name:etymology:wikidata", + "description": "The MapComplete theme Open Etymology Map has a layer Has etymolgy showing features with this tag" + }, + { + "key": "name:etymology", + "description": "The MapComplete theme Open Etymology Map has a layer Has etymolgy showing features with this tag" + }, + { + "key": "image", + "description": "The layer 'Has etymolgy shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Has etymolgy shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Has etymolgy shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Has etymolgy shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "name:etymology:wikidata", + "description": "Layer 'Has etymolgy' shows and asks freeform values for key 'name:etymology:wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Has etymolgy' shows and asks freeform values for key 'name:etymology' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Has etymolgy' shows name:etymology=unknown with a fixed text, namely 'The origin of this name is unknown in all literature' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Etymology Map')", + "value": "unknown" + }, + { + "key": "image", + "description": "The layer 'Has etymolgy allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Has etymolgy allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Has etymolgy allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Has etymolgy allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "Layer 'Has etymolgy' shows and asks freeform values for key 'wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "wikidata", + "description": "Layer 'Has etymolgy' shows wikidata= with a fixed text, namely 'No Wikipedia page has been linked yet' (in the MapComplete.osm.be theme 'Open Etymology Map') Picking this answer will delete the key wikidata.", + "value": "" + }, + { + "key": "name", + "description": "The MapComplete theme Open Etymology Map has a layer Streets without etymology information showing features with this tag" + }, + { + "key": "highway", + "description": "The MapComplete theme Open Etymology Map has a layer Streets without etymology information showing features with this tag" + }, + { + "key": "image", + "description": "The layer 'Streets without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Streets without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Streets without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Streets without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "name:etymology:wikidata", + "description": "Layer 'Streets without etymology information' shows and asks freeform values for key 'name:etymology:wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Streets without etymology information' shows and asks freeform values for key 'name:etymology' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Streets without etymology information' shows name:etymology=unknown with a fixed text, namely 'The origin of this name is unknown in all literature' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Etymology Map')", + "value": "unknown" + }, + { + "key": "image", + "description": "The layer 'Streets without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Streets without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Streets without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Streets without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "Layer 'Streets without etymology information' shows and asks freeform values for key 'wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "wikidata", + "description": "Layer 'Streets without etymology information' shows wikidata= with a fixed text, namely 'No Wikipedia page has been linked yet' (in the MapComplete.osm.be theme 'Open Etymology Map') Picking this answer will delete the key wikidata.", + "value": "" + }, + { + "key": "name", + "description": "The MapComplete theme Open Etymology Map has a layer Parks and forests without etymology information showing features with this tag" + }, + { + "key": "leisure", + "description": "The MapComplete theme Open Etymology Map has a layer Parks and forests without etymology information showing features with this tag", + "value": "park" + }, + { + "key": "landuse", + "description": "The MapComplete theme Open Etymology Map has a layer Parks and forests without etymology information showing features with this tag", + "value": "forest" + }, + { + "key": "image", + "description": "The layer 'Parks and forests without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Parks and forests without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Parks and forests without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Parks and forests without etymology information shows images based on the keys image, image:0, image:1,... and wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "name:etymology:wikidata", + "description": "Layer 'Parks and forests without etymology information' shows and asks freeform values for key 'name:etymology:wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Parks and forests without etymology information' shows and asks freeform values for key 'name:etymology' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "name:etymology", + "description": "Layer 'Parks and forests without etymology information' shows name:etymology=unknown with a fixed text, namely 'The origin of this name is unknown in all literature' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Etymology Map')", + "value": "unknown" + }, + { + "key": "image", + "description": "The layer 'Parks and forests without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Parks and forests without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Parks and forests without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Parks and forests without etymology information allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "Layer 'Parks and forests without etymology information' shows and asks freeform values for key 'wikidata' (in the MapComplete.osm.be theme 'Open Etymology Map')" + }, + { + "key": "wikidata", + "description": "Layer 'Parks and forests without etymology information' shows wikidata= with a fixed text, namely 'No Wikipedia page has been linked yet' (in the MapComplete.osm.be theme 'Open Etymology Map') Picking this answer will delete the key wikidata.", + "value": "" + } + ] +} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_food.json b/Docs/TagInfo/mapcomplete_food.json index 2ae75a8ff..4666bf436 100644 --- a/Docs/TagInfo/mapcomplete_food.json +++ b/Docs/TagInfo/mapcomplete_food.json @@ -76,6 +76,16 @@ "description": "Layer 'Restaurants and fast food' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')", "value": "yes" }, + { + "key": "payment:app", + "description": "Layer 'Restaurants and fast food' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')", + "value": "yes" + }, + { + "key": "payment:membership_card", + "description": "Layer 'Restaurants and fast food' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')", + "value": "yes" + }, { "key": "wheelchair", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapated for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')", diff --git a/Docs/TagInfo/mapcomplete_fritures.json b/Docs/TagInfo/mapcomplete_fritures.json index ae5de3180..54b743f87 100644 --- a/Docs/TagInfo/mapcomplete_fritures.json +++ b/Docs/TagInfo/mapcomplete_fritures.json @@ -81,6 +81,16 @@ "description": "Layer 'Fries shop' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", "value": "yes" }, + { + "key": "payment:app", + "description": "Layer 'Fries shop' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", + "value": "yes" + }, + { + "key": "payment:membership_card", + "description": "Layer 'Fries shop' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", + "value": "yes" + }, { "key": "wheelchair", "description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapated for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", @@ -396,6 +406,16 @@ "description": "Layer 'Restaurants and fast food' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", "value": "yes" }, + { + "key": "payment:app", + "description": "Layer 'Restaurants and fast food' shows payment:app=yes with a fixed text, namely 'Payment is done using a dedicated app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", + "value": "yes" + }, + { + "key": "payment:membership_card", + "description": "Layer 'Restaurants and fast food' shows payment:membership_card=yes with a fixed text, namely 'Payment is done using a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", + "value": "yes" + }, { "key": "wheelchair", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapated for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", diff --git a/Docs/TagInfo/mapcomplete_maps.json b/Docs/TagInfo/mapcomplete_maps.json index 8fe815f50..be108e5c1 100644 --- a/Docs/TagInfo/mapcomplete_maps.json +++ b/Docs/TagInfo/mapcomplete_maps.json @@ -74,25 +74,6 @@ "key": "map_source:attribution", "description": "Layer 'Maps' shows map_source:attribution=no with a fixed text, namely 'There is no attribution at all' (in the MapComplete.osm.be theme 'A map of maps')", "value": "no" - }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Maps' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'A map of maps')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'A map of maps')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'A map of maps')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'A map of maps')", - "value": "yes" } ] } \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_nature.json b/Docs/TagInfo/mapcomplete_nature.json index c6782c08f..75fc1ec2d 100644 --- a/Docs/TagInfo/mapcomplete_nature.json +++ b/Docs/TagInfo/mapcomplete_nature.json @@ -60,25 +60,6 @@ "description": "Layer 'Drinking water' shows bottle=no with a fixed text, namely 'Water bottles may not fit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", "value": "no" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Drinking water' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'De Natuur in')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Drinking water' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "yes" - }, { "key": "leisure", "description": "The MapComplete theme De Natuur in has a layer Vogelkijkhutten showing features with this tag", @@ -189,25 +170,6 @@ "description": "Layer 'Vogelkijkhutten' shows operator=Agentschap Natuur en Bos with a fixed text, namely 'Beheer door het Agentschap Natuur en Bos ' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", "value": "Agentschap Natuur en Bos" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Vogelkijkhutten' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'De Natuur in')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Vogelkijkhutten' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Vogelkijkhutten' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Vogelkijkhutten' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "yes" - }, { "key": "tourism", "description": "The MapComplete theme De Natuur in has a layer Maps showing features with this tag", @@ -273,25 +235,6 @@ "description": "Layer 'Maps' shows map_source:attribution=no with a fixed text, namely 'There is no attribution at all' (in the MapComplete.osm.be theme 'De Natuur in')", "value": "no" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Maps' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'De Natuur in')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Maps' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "yes" - }, { "key": "information", "description": "The MapComplete theme De Natuur in has a layer Information boards showing features with this tag", @@ -313,25 +256,6 @@ "key": "wikipedia", "description": "The layer 'Information boards allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Information boards' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'De Natuur in')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Information boards' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Information boards' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Information boards' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "yes" - }, { "key": "leisure", "description": "The MapComplete theme De Natuur in has a layer Natuurgebied showing features with this tag", @@ -505,25 +429,6 @@ "key": "wikidata", "description": "Layer 'Natuurgebied' shows wikidata= with a fixed text, namely 'No Wikipedia page has been linked yet' (in the MapComplete.osm.be theme 'De Natuur in') Picking this answer will delete the key wikidata.", "value": "" - }, - { - "key": "service:bicycle:cleaning:charge", - "description": "Layer 'Natuurgebied' shows and asks freeform values for key 'service:bicycle:cleaning:charge' (in the MapComplete.osm.be theme 'De Natuur in')" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Natuurgebied' shows service:bicycle:cleaning:fee=no&service:bicycle:cleaning:charge= with a fixed text, namely 'The cleaning service is free to use' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&service:bicycle:cleaning:charge=" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Natuurgebied' shows service:bicycle:cleaning:fee=no& with a fixed text, namely 'Free to use' (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "no&" - }, - { - "key": "service:bicycle:cleaning:fee", - "description": "Layer 'Natuurgebied' shows service:bicycle:cleaning:fee=yes with a fixed text, namely 'The cleaning service has a fee' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'De Natuur in')", - "value": "yes" } ] } \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_waste_basket.json b/Docs/TagInfo/mapcomplete_waste_basket.json index eeebd2e5b..1bbaca8ca 100644 --- a/Docs/TagInfo/mapcomplete_waste_basket.json +++ b/Docs/TagInfo/mapcomplete_waste_basket.json @@ -44,6 +44,31 @@ "key": "waste", "description": "Layer 'Waste Basket' shows waste=sharps with a fixed text, namely 'A waste basket for needles and other sharp objects' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket')", "value": "sharps" + }, + { + "key": "vending", + "description": "Layer 'Waste Basket' shows vending=dog_excrement_bag¬:vending= with a fixed text, namely 'This waste basket has a dispenser for (dog) excrement bags' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket')", + "value": "dog_excrement_bag" + }, + { + "key": "not:vending", + "description": "Layer 'Waste Basket' shows vending=dog_excrement_bag¬:vending= with a fixed text, namely 'This waste basket has a dispenser for (dog) excrement bags' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket') Picking this answer will delete the key not:vending.", + "value": "" + }, + { + "key": "not:vending", + "description": "Layer 'Waste Basket' shows not:vending=dog_excrement_bag&vending= with a fixed text, namely 'This waste basket does not have a dispenser for (dog) excrement bags' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket')", + "value": "dog_excrement_bag" + }, + { + "key": "vending", + "description": "Layer 'Waste Basket' shows not:vending=dog_excrement_bag&vending= with a fixed text, namely 'This waste basket does not have a dispenser for (dog) excrement bags' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket') Picking this answer will delete the key vending.", + "value": "" + }, + { + "key": "vending", + "description": "Layer 'Waste Basket' shows vending= with a fixed text, namely 'This waste basket does not have a dispenser for (dog) excrement bags' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste Basket') Picking this answer will delete the key vending.", + "value": "" } ] } \ No newline at end of file diff --git a/Docs/Tools/centerpoints.geojson b/Docs/Tools/centerpoints.geojson index 0e8cb4971..b1788a37b 100644 --- a/Docs/Tools/centerpoints.geojson +++ b/Docs/Tools/centerpoints.geojson @@ -26175,6 +26175,1556 @@ ] } }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.57145815, + 53.01847585 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.7512944, + 49.4243983 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -111.6257411, + 37.55735975 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2207445, + 51.2170027 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 12.772265, + 53.160239000000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 11.90339645, + 48.3117862 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 15.9909192, + 45.803434949999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -1.1522394500000002, + 52.957823149999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -1.56146395, + 54.842731549999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2111955500000002, + 51.2084446 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.55397325, + 53.1172933 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.17585055, + 51.19023905 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.336863, + 51.538337 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 12.76875425, + 53.1585946 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5710394999999995, + 53.01940435 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.51085765, + 53.001246050000006 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.36371535, + 50.8215982 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.912086850000001, + 53.73600945 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.22362865, + 50.89678165 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -3.9183158, + 43.3116971 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.5911317, + 50.8997234 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.562534149999999, + 53.016830150000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2551906, + 50.8356085 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.9058252, + 53.60786 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.3855936, + 47.721562 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.14034775, + 51.16952665 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.7575386, + 49.4237877 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.566961, + 53.0171375 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.5884734, + 47.5827878 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 149.0730838, + -35.236471449999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.1321904, + 43.6495348 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.3029667, + 51.6403353 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.3719193, + 47.6787492 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.2374387000000002, + 41.4659886 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.7526418, + 50.8032604 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.55760685, + 53.0197808 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.5661171, + 49.86048745 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.31659155, + 51.6435015 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.5660771, + 49.8604105 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5703112, + 53.019947450000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 13.5652936, + 51.9702387 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.560159, + 53.0218087 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.22622465, + 51.215635750000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.2773693, + 51.1411478 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.1008119, + 52.099058299999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.0932216, + 52.10242865 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.876496449999999, + 45.469509200000005 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.1485138, + 55.6929421 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -76.6471508, + 39.3191368 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -85.4725958, + 32.5885088 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -2.1164584499999997, + 52.805281449999995 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2301747499999998, + 51.21353155 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2376945, + 51.2149454 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5489672, + 53.00156845 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2243017, + 51.21417825 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2399328, + 51.2127007 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.23990685, + 51.21273015 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.23439405, + 51.2142788 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.2369476, + 50.7326341 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 14.6420013, + 52.0122072 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.18013695, + 50.6701891 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.2516995, + 41.4479352 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.192269250000001, + 50.64757205 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.1848693, + 50.6465303 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.1960489, + 50.641809699999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.1838941, + 50.6471167 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.1618764, + 51.18931585 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.237775, + 41.464776549999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.38805895, + 51.16272495 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.1567132, + 51.1771083 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 14.6371402, + 52.0175094 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.205695, + 51.199634700000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2051369999999997, + 51.200769699999995 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.249359800000001, + 50.7380632 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.3294368, + 50.9907606 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 121.031891, + 14.6058991 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.8219139, + 47.6452408 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.8237334, + 47.6456871 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.82290715, + 47.6486087 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5610204, + 53.020174350000005 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 12.8910801, + 53.1005661 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.2392313, + 50.7322621 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.59530655, + 53.219854850000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.54719435, + 52.984896899999995 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.8254708, + 50.883697350000006 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5710559, + 53.0194197 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -9.113818850000001, + 53.26469535 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.2694622, + 47.7112794 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.22073675, + 51.2166899 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.59041635, + 53.215512450000006 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.22112885, + 51.21348495 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.22629595, + 51.212732450000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.8807272, + 51.0598072 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.71769175, + 51.0433044 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.7110539, + 51.0423597 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.5757919, + 50.6486646 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.5887897, + 47.669135499999996 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2185596, + 51.2131485 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.2315223499999997, + 41.44713725 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2162637, + 51.20911015 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2172443499999996, + 51.21327325 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.6043359, + 53.2170286 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.55694655, + 53.015631299999995 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.8807272, + 51.0598072 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.2396974, + 50.73752155 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2300254, + 51.21528395 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.5722951, + 50.6552402 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.2992904, + 51.468360450000006 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5650013, + 53.02120915 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -9.0755173, + 53.2752413 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -9.0888201, + 53.26975745 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -9.0455576, + 53.2748582 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.2694676, + 45.5034727 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.21815375, + 51.21761825 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -75.5030324, + 5.01846405 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.5719056, + 52.9881679 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 6.561177900000001, + 53.0194654 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2153756500000004, + 51.2156508 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 11.93157325, + 57.687017350000005 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 14.480027849999999, + 51.93363535 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.28114195, + 47.7105653 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.274070250000001, + 47.6921054 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.0720514, + 50.8425665 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2032195, + 51.216655700000004 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.2723456, + 47.6947613 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.7449888, + 50.2991705 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 9.1566274, + 45.572282349999995 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.74540975, + 45.1502146 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.616621, + 44.827888 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.33639875, + 49.51315865 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.122651449999999, + 52.0783552 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -3.3341011, + 52.275169 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.37134710000001, + 47.6895039 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.4458634, + 49.0259931 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 5.5603149, + 49.7179279 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.03050115, + 49.7243959 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.2097007, + 41.536822 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2860721, + 51.3408964 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 4.403972749999999, + 50.8373068 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.6153278, + 51.9250815 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.61919675, + 51.9271727 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 8.4225882, + 47.3830851 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.6206936, + 51.931771 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 14.9882162, + 51.1523834 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.5786386, + 47.55422325 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.7511352, + 49.4252185 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.7597241, + 49.4281582 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -73.23423980000001, + -39.8222105 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 2.3109959, + 48.83266545 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 18.5302743, + 54.5212696 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 174.7825092, + -36.85054 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.2186897, + 51.2131816 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.4250032, + 51.1032661 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.3966982000000003, + 51.09422865 + ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 3.3796369, + 51.10523445 + ] + } + }, { "type": "Feature", "geometry": { diff --git a/Docs/Tools/graphs/Changesets per host in 2021.png b/Docs/Tools/graphs/Changesets per host in 2021.png index eb12a22f2..3d25a958e 100644 Binary files a/Docs/Tools/graphs/Changesets per host in 2021.png and b/Docs/Tools/graphs/Changesets per host in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per host.png b/Docs/Tools/graphs/Changesets per host.png index d531ffabf..1dd4a2ecf 100644 Binary files a/Docs/Tools/graphs/Changesets per host.png and b/Docs/Tools/graphs/Changesets per host.png differ diff --git a/Docs/Tools/graphs/Changesets per theme (bar) in 2021.png b/Docs/Tools/graphs/Changesets per theme (bar) in 2021.png index 6371b5849..c20e8357b 100644 Binary files a/Docs/Tools/graphs/Changesets per theme (bar) in 2021.png and b/Docs/Tools/graphs/Changesets per theme (bar) in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per theme (bar).png b/Docs/Tools/graphs/Changesets per theme (bar).png index 987c9e0ad..a3d920de7 100644 Binary files a/Docs/Tools/graphs/Changesets per theme (bar).png and b/Docs/Tools/graphs/Changesets per theme (bar).png differ diff --git a/Docs/Tools/graphs/Changesets per theme (pie) in 2021.png b/Docs/Tools/graphs/Changesets per theme (pie) in 2021.png index 77f358409..8b1524bc7 100644 Binary files a/Docs/Tools/graphs/Changesets per theme (pie) in 2021.png and b/Docs/Tools/graphs/Changesets per theme (pie) in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per theme (pie).png b/Docs/Tools/graphs/Changesets per theme (pie).png index 6a31bcaab..5ae1dc7c0 100644 Binary files a/Docs/Tools/graphs/Changesets per theme (pie).png and b/Docs/Tools/graphs/Changesets per theme (pie).png differ diff --git a/Docs/Tools/graphs/Changesets per theme in 2021.png b/Docs/Tools/graphs/Changesets per theme in 2021.png index 330f4a0cd..8cfe94596 100644 Binary files a/Docs/Tools/graphs/Changesets per theme in 2021.png and b/Docs/Tools/graphs/Changesets per theme in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per theme.png b/Docs/Tools/graphs/Changesets per theme.png index 6e7a1ff64..0d67bafad 100644 Binary files a/Docs/Tools/graphs/Changesets per theme.png and b/Docs/Tools/graphs/Changesets per theme.png differ diff --git a/Docs/Tools/graphs/Changesets per version number in 2021.png b/Docs/Tools/graphs/Changesets per version number in 2021.png index d4c1da71e..0641d2a4f 100644 Binary files a/Docs/Tools/graphs/Changesets per version number in 2021.png and b/Docs/Tools/graphs/Changesets per version number in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per version number.png b/Docs/Tools/graphs/Changesets per version number.png index 1eb7de6ac..bf6ccc351 100644 Binary files a/Docs/Tools/graphs/Changesets per version number.png and b/Docs/Tools/graphs/Changesets per version number.png differ diff --git a/Docs/Tools/graphs/Contributors per changeset count in 2021.png b/Docs/Tools/graphs/Contributors per changeset count in 2021.png index 50b25ed4e..87c5a63e1 100644 Binary files a/Docs/Tools/graphs/Contributors per changeset count in 2021.png and b/Docs/Tools/graphs/Contributors per changeset count in 2021.png differ diff --git a/Docs/Tools/graphs/Contributors per changeset count.png b/Docs/Tools/graphs/Contributors per changeset count.png index 8ca4d7c2f..54b273981 100644 Binary files a/Docs/Tools/graphs/Contributors per changeset count.png and b/Docs/Tools/graphs/Contributors per changeset count.png differ diff --git a/Docs/Tools/graphs/Contributors per day in 2021.png b/Docs/Tools/graphs/Contributors per day in 2021.png index a29b93d5b..b4df69230 100644 Binary files a/Docs/Tools/graphs/Contributors per day in 2021.png and b/Docs/Tools/graphs/Contributors per day in 2021.png differ diff --git a/Docs/Tools/graphs/Contributors per day.png b/Docs/Tools/graphs/Contributors per day.png index 0ac57f754..aaa99887f 100644 Binary files a/Docs/Tools/graphs/Contributors per day.png and b/Docs/Tools/graphs/Contributors per day.png differ diff --git a/Docs/Tools/graphs/Empty changesets by date.png b/Docs/Tools/graphs/Empty changesets by date.png index a269c0d23..f29901914 100644 Binary files a/Docs/Tools/graphs/Empty changesets by date.png and b/Docs/Tools/graphs/Empty changesets by date.png differ diff --git a/Docs/Tools/stats/stats.2021-10.json b/Docs/Tools/stats/stats.2021-10.json index 26258f52b..5cfb485ec 100644 --- a/Docs/Tools/stats/stats.2021-10.json +++ b/Docs/Tools/stats/stats.2021-10.json @@ -1,5 +1,9747 @@ { "features": [ + { + "id": 112977842, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5714162, + 53.018317 + ], + [ + 6.5715001, + 53.018317 + ], + [ + 6.5715001, + 53.0186347 + ], + [ + 6.5714162, + 53.0186347 + ], + [ + 6.5714162, + 53.018317 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.2", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-26T06:44:48Z", + "reviewed_features": [], + "create": 0, + "modify": 6, + "delete": 0, + "area": 2.66550299996821e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 14, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112977275, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.7512944, + 49.4243983 + ], + [ + 7.7512944, + 49.4243983 + ], + [ + 7.7512944, + 49.4243983 + ], + [ + 7.7512944, + 49.4243983 + ], + [ + 7.7512944, + 49.4243983 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Magnus1234", + "uid": "14305060", + "editor": "MapComplete 0.11.2", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-26T06:27:31Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "create": 1, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112972777, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -118.4686247, + 33.9686292 + ], + [ + -104.7828575, + 33.9686292 + ], + [ + -104.7828575, + 41.1460903 + ], + [ + -118.4686247, + 41.1460903 + ], + [ + -118.4686247, + 33.9686292 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "millysoose", + "uid": "12537223", + "editor": "MapComplete 0.11.2", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-26T03:04:32Z", + "reviewed_features": [], + "create": 0, + "modify": 39, + "delete": 0, + "area": 98.2290617016559, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 53, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112970293, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2207445, + 51.2170027 + ], + [ + 3.2207445, + 51.2170027 + ], + [ + 3.2207445, + 51.2170027 + ], + [ + 3.2207445, + 51.2170027 + ], + [ + 3.2207445, + 51.2170027 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-25T23:30:47Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 5, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112966235, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 12.7626281, + 53.1568885 + ], + [ + 12.7819019, + 53.1568885 + ], + [ + 12.7819019, + 53.1635895 + ], + [ + 12.7626281, + 53.1635895 + ], + [ + 12.7626281, + 53.1568885 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Wesomat87", + "uid": "14348684", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T20:31:57Z", + "reviewed_features": [], + "create": 3, + "modify": 2, + "delete": 0, + "area": 0.000129153733799985, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "www.waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112965511, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 11.8934615, + 48.3073035 + ], + [ + 11.9133314, + 48.3073035 + ], + [ + 11.9133314, + 48.3162689 + ], + [ + 11.8934615, + 48.3162689 + ], + [ + 11.8934615, + 48.3073035 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "DoubleA", + "uid": "1198074", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T20:11:08Z", + "reviewed_features": [], + "create": 0, + "modify": 21, + "delete": 0, + "area": 0.000178141601459877, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 32, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112965061, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 15.9775518, + 45.7945704 + ], + [ + 16.0042866, + 45.7945704 + ], + [ + 16.0042866, + 45.8122995 + ], + [ + 15.9775518, + 45.8122995 + ], + [ + 15.9775518, + 45.7945704 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Janjko", + "uid": "244754", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T20:00:03Z", + "reviewed_features": [], + "create": 0, + "modify": 101, + "delete": 0, + "area": 0.000473983942680102, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 180, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112964829, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -1.1557531, + 52.957285 + ], + [ + -1.1487258, + 52.957285 + ], + [ + -1.1487258, + 52.9583613 + ], + [ + -1.1557531, + 52.9583613 + ], + [ + -1.1557531, + 52.957285 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "doublah", + "uid": "4948143", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-25T19:53:59Z", + "reviewed_features": [], + "create": 0, + "modify": 10, + "delete": 0, + "area": 0.00000756348299000809, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 11, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112964711, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -1.5797011, + 54.8337195 + ], + [ + -1.5432268, + 54.8337195 + ], + [ + -1.5432268, + 54.8517436 + ], + [ + -1.5797011, + 54.8517436 + ], + [ + -1.5797011, + 54.8337195 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "confusedbuffalo", + "uid": "242345", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T19:50:33Z", + "reviewed_features": [], + "create": 0, + "modify": 14, + "delete": 0, + "area": 0.000657416430629932, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 18, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112953730, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2014525, + 51.1928237 + ], + [ + 3.2209386, + 51.1928237 + ], + [ + 3.2209386, + 51.2240655 + ], + [ + 3.2014525, + 51.2240655 + ], + [ + 3.2014525, + 51.1928237 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T15:40:45Z", + "reviewed_features": [], + "create": 0, + "modify": 17, + "delete": 0, + "area": 0.000608780838980083, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "grb", + "answer": 22, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112953572, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5365292, + 53.0175187 + ], + [ + 6.5714173, + 53.0175187 + ], + [ + 6.5714173, + 53.2170679 + ], + [ + 6.5365292, + 53.2170679 + ], + [ + 6.5365292, + 53.0175187 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-25T15:37:09Z", + "reviewed_features": [], + "create": 0, + "modify": 34, + "delete": 0, + "area": 0.00696189244452022, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 48, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112953457, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.1752036, + 51.1897954 + ], + [ + 3.1764975, + 51.1897954 + ], + [ + 3.1764975, + 51.1906827 + ], + [ + 3.1752036, + 51.1906827 + ], + [ + 3.1752036, + 51.1897954 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T15:34:38Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.00000114807747000278, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 4, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112952854, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 13.336863, + 51.538337 + ], + [ + 13.336863, + 51.538337 + ], + [ + 13.336863, + 51.538337 + ], + [ + 13.336863, + 51.538337 + ], + [ + 13.336863, + 51.538337 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "da-werbung", + "uid": "4923577", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T15:20:15Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112952535, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 12.7572405, + 53.1569431 + ], + [ + 12.780268, + 53.1569431 + ], + [ + 12.780268, + 53.1602461 + ], + [ + 12.7572405, + 53.1602461 + ], + [ + 12.7572405, + 53.1569431 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Wesomat87", + "uid": "14348684", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T15:12:52Z", + "reviewed_features": [], + "create": 7, + "modify": 5, + "delete": 0, + "area": 0.000076059832500056, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "www.waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112949077, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5703946, + 53.0177149 + ], + [ + 6.5716844, + 53.0177149 + ], + [ + 6.5716844, + 53.0210938 + ], + [ + 6.5703946, + 53.0210938 + ], + [ + 6.5703946, + 53.0177149 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-25T13:52:37Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0.00000435810521999982, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 6, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112945227, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5108545, + 53.0012411 + ], + [ + 6.5108608, + 53.0012411 + ], + [ + 6.5108608, + 53.001251 + ], + [ + 6.5108545, + 53.001251 + ], + [ + 6.5108545, + 53.0012411 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting_assen", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-25T12:24:30Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 6.23700000116805e-11, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "move": 1, + "path": "mc/theme/street_lighting/", + "theme": "street_lighting_assen", + "imagery": "Actueel_ortho25_WMS", + "language": "nl", + "move:node/9187383555": "The location of this object is inaccurate and should be moved a few meter" + } + } + }, + { + "id": 112928541, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.325695, + 50.820308 + ], + [ + 4.4017357, + 50.820308 + ], + [ + 4.4017357, + 50.8228884 + ], + [ + 4.325695, + 50.8228884 + ], + [ + 4.325695, + 50.820308 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "DAKAR01", + "uid": "14345865", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-25T05:06:23Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 1, + "area": 0.000196215422279952, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 3, + "imagery": "CartoDB.Voyager", + "deletion": 1, + "language": "fr", + "deletion:node/9119877457": "duplicate" + } + } + }, + { + "id": 112918213, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.9118367, + 53.7358953 + ], + [ + 9.912337, + 53.7358953 + ], + [ + 9.912337, + 53.7361236 + ], + [ + 9.9118367, + 53.7361236 + ], + [ + 9.9118367, + 53.7358953 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Unkn0wnKevin", + "uid": "1855021", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #playgrounds", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-24T19:00:58Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 1.14218489998275e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "playgrounds", + "answer": 2, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112916108, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.2174595, + 50.8954504 + ], + [ + 4.2297978, + 50.8954504 + ], + [ + 4.2297978, + 50.8981129 + ], + [ + 4.2174595, + 50.8981129 + ], + [ + 4.2174595, + 50.8954504 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Thierry1030", + "uid": "286563", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclestreets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-24T18:00:42Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 0.0000328507237499938, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclestreets", + "answer": 4, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112913626, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -3.9183158, + 43.3116971 + ], + [ + -3.9183158, + 43.3116971 + ], + [ + -3.9183158, + 43.3116971 + ], + [ + -3.9183158, + 43.3116971 + ], + [ + -3.9183158, + 43.3116971 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Ninopiña10", + "uid": "11138282", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #drinking_water", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T16:46:44Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "drinking_water", + "answer": 2, + "create": 1, + "imagery": "osm", + "language": "en", + "add-image": 1 + } + } + }, + { + "id": 112910601, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.5911317, + 50.8997234 + ], + [ + 3.5911317, + 50.8997234 + ], + [ + 3.5911317, + 50.8997234 + ], + [ + 3.5911317, + 50.8997234 + ], + [ + 3.5911317, + 50.8997234 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T15:22:13Z", + "reviewed_features": [], + "create": 1, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "answer": 7, + "create": 1, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112908415, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5520187, + 53.0127475 + ], + [ + 6.5730496, + 53.0127475 + ], + [ + 6.5730496, + 53.0209128 + ], + [ + 6.5520187, + 53.0209128 + ], + [ + 6.5520187, + 53.0127475 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting_assen", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T14:27:05Z", + "reviewed_features": [], + "create": 18, + "modify": 33, + "delete": 0, + "area": 0.00017172360776989, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "move": 9, + "path": "mc/theme/street_lighting/", + "theme": "street_lighting_assen", + "answer": 54, + "create": 18, + "imagery": "Actueel_ortho25_WMS", + "language": "en", + "move:node/7947792102": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7947922318": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7947922354": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7947939127": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7948227641": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7948606596": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7948764089": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7950537416": "The location of this object is inaccurate and should be moved a few meter", + "move:node/7950537499": "The location of this object is inaccurate and should be moved a few meter" + } + } + }, + { + "id": 112908056, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2551906, + 50.8356085 + ], + [ + 3.2551906, + 50.8356085 + ], + [ + 3.2551906, + 50.8356085 + ], + [ + 3.2551906, + 50.8356085 + ], + [ + 3.2551906, + 50.8356085 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T14:17:55Z", + "reviewed_features": [], + "create": 1, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "cyclofix", + "answer": 4, + "create": 1, + "imagery": "CartoDB.Voyager", + "language": "nl", + "add-image": 2 + } + } + }, + { + "id": 112902452, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.9058252, + 53.60786 + ], + [ + 9.9058252, + 53.60786 + ], + [ + 9.9058252, + 53.60786 + ], + [ + 9.9058252, + 53.60786 + ], + [ + 9.9058252, + 53.60786 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "dkf2010", + "uid": "685599", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T11:32:24Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "answer": 5, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112902215, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.3855936, + 47.721562 + ], + [ + 9.3855936, + 47.721562 + ], + [ + 9.3855936, + 47.721562 + ], + [ + 9.3855936, + 47.721562 + ], + [ + 9.3855936, + 47.721562 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-24T11:24:10Z", + "reviewed_features": [], + "create": 1, + "modify": 5, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "answer": 7, + "create": 1, + "imagery": "osm", + "language": "de", + "add-image": 1 + } + } + }, + { + "id": 112900519, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.1401296, + 51.1689705 + ], + [ + 4.1405659, + 51.1689705 + ], + [ + 4.1405659, + 51.1700828 + ], + [ + 4.1401296, + 51.1700828 + ], + [ + 4.1401296, + 51.1689705 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclestreets", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T10:33:13Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 4.85296490001699e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclestreets", + "answer": 1, + "imagery": "osm", + "language": "nl", + "add-image": 1 + } + } + }, + { + "id": 112900339, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.7575386, + 49.4237877 + ], + [ + 7.7575386, + 49.4237877 + ], + [ + 7.7575386, + 49.4237877 + ], + [ + 7.7575386, + 49.4237877 + ], + [ + 7.7575386, + 49.4237877 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Magnus1234", + "uid": "14305060", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T10:29:26Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 2, + "create": 1, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112899746, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5613364, + 53.0158959 + ], + [ + 6.5725856, + 53.0158959 + ], + [ + 6.5725856, + 53.0183791 + ], + [ + 6.5613364, + 53.0183791 + ], + [ + 6.5613364, + 53.0158959 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting_assen", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T10:14:32Z", + "reviewed_features": [], + "create": 4, + "modify": 2, + "delete": 0, + "area": 0.0000279340134400039, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting_assen", + "answer": 11, + "create": 4, + "imagery": "Actueel_ortho25_WMS", + "language": "en" + } + } + }, + { + "id": 112893211, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.5884734, + 47.5827878 + ], + [ + 7.5884734, + 47.5827878 + ], + [ + 7.5884734, + 47.5827878 + ], + [ + 7.5884734, + 47.5827878 + ], + [ + 7.5884734, + 47.5827878 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "KaiPankrath", + "uid": "4067009", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #drinking_water", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-24T05:01:23Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "drinking_water", + "answer": 1, + "imagery": "CartoDB.Voyager", + "language": "en", + "add-image": 1 + } + } + }, + { + "id": 112892700, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 149.0714362, + -35.2370494 + ], + [ + 149.0747314, + -35.2370494 + ], + [ + 149.0747314, + -35.2358935 + ], + [ + 149.0714362, + -35.2358935 + ], + [ + 149.0714362, + -35.2370494 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "JoeG", + "uid": "73276", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #toilets", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-24T04:22:01Z", + "reviewed_features": [], + "create": 1, + "modify": 3, + "delete": 0, + "area": 0.00000380892167997446, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "toilets", + "answer": 8, + "create": 1, + "imagery": "osm", + "language": "en", + "add-image": 1 + } + } + }, + { + "id": 112890047, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.1321904, + 43.6495348 + ], + [ + 7.1321904, + 43.6495348 + ], + [ + 7.1321904, + 43.6495348 + ], + [ + 7.1321904, + 43.6495348 + ], + [ + 7.1321904, + 43.6495348 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Kalepom", + "uid": "392288", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #ghostbikes", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T23:37:42Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "ghostbikes", + "answer": 1, + "imagery": "CartoDB.Positron", + "language": "fr" + } + } + }, + { + "id": 112888673, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 13.3029667, + 51.6403353 + ], + [ + 13.3029667, + 51.6403353 + ], + [ + 13.3029667, + 51.6403353 + ], + [ + 13.3029667, + 51.6403353 + ], + [ + 13.3029667, + 51.6403353 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "da-werbung", + "uid": "4923577", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T22:02:36Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112886005, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.3719196, + 47.6787492 + ], + [ + -122.371919, + 47.6787492 + ], + [ + -122.371919, + 47.6787492 + ], + [ + -122.3719196, + 47.6787492 + ], + [ + -122.3719196, + 47.6787492 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Wim L", + "uid": "223681", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T19:48:47Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "answer": 3, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112885975, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2370934, + 41.4647994 + ], + [ + 2.237784, + 41.4647994 + ], + [ + 2.237784, + 41.4671778 + ], + [ + 2.2370934, + 41.4671778 + ], + [ + 2.2370934, + 41.4647994 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "PabloDíaz", + "uid": "14309824", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #https://llefia.org/arbres/mapcomplete1.json", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T19:47:42Z", + "reviewed_features": [], + "create": 3, + "modify": 11, + "delete": 0, + "area": 0.00000164252304000334, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://llefia.org/arbres/mapcomplete1.json", + "answer": 14, + "create": 3, + "imagery": "HDM_HOT", + "language": "ca", + "add-image": 6 + } + } + }, + { + "id": 112883053, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.7526418, + 50.8032604 + ], + [ + 3.7526418, + 50.8032604 + ], + [ + 3.7526418, + 50.8032604 + ], + [ + 3.7526418, + 50.8032604 + ], + [ + 3.7526418, + 50.8032604 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T17:48:38Z", + "reviewed_features": [], + "create": 1, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "answer": 9, + "create": 1, + "imagery": "CartoDB.Voyager", + "language": "nl" + } + } + }, + { + "id": 112881713, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.548624, + 53.0176741 + ], + [ + 6.5665897, + 53.0176741 + ], + [ + 6.5665897, + 53.0218875 + ], + [ + 6.548624, + 53.0218875 + ], + [ + 6.548624, + 53.0176741 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T17:02:43Z", + "reviewed_features": [], + "create": 0, + "modify": 27, + "delete": 0, + "area": 0.0000756966803799547, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 39, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112879502, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.5661162, + 49.8604591 + ], + [ + 8.566118, + 49.8604591 + ], + [ + 8.566118, + 49.8605158 + ], + [ + 8.5661162, + 49.8605158 + ], + [ + 8.5661162, + 49.8604591 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "rosmarin", + "uid": "458474", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #surveillance", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T15:54:23Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 1.02059999987284e-10, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "surveillance", + "answer": 2, + "imagery": "osm", + "language": "de" + } + } + }, + { + "id": 112879289, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 13.3162739, + 51.633032 + ], + [ + 13.3169092, + 51.633032 + ], + [ + 13.3169092, + 51.653971 + ], + [ + 13.3162739, + 51.653971 + ], + [ + 13.3162739, + 51.633032 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "da-werbung", + "uid": "4923577", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T15:48:41Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.0000133025466999782, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112879190, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.5660771, + 49.8604105 + ], + [ + 8.5660771, + 49.8604105 + ], + [ + 8.5660771, + 49.8604105 + ], + [ + 8.5660771, + 49.8604105 + ], + [ + 8.5660771, + 49.8604105 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "rosmarin", + "uid": "458474", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #surveillance", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T15:45:08Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "surveillance", + "create": 1, + "imagery": "osm", + "language": "de" + } + } + }, + { + "id": 112877710, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5701013, + 53.0198683 + ], + [ + 6.5705211, + 53.0198683 + ], + [ + 6.5705211, + 53.0200266 + ], + [ + 6.5701013, + 53.0200266 + ], + [ + 6.5701013, + 53.0198683 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T15:04:04Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 6.64543400009737e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 12, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112875924, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 13.5652936, + 51.9702387 + ], + [ + 13.5652936, + 51.9702387 + ], + [ + 13.5652936, + 51.9702387 + ], + [ + 13.5652936, + 51.9702387 + ], + [ + 13.5652936, + 51.9702387 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Firefighter-112", + "uid": "14014754", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T14:13:22Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112873649, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.560159, + 53.0218087 + ], + [ + 6.560159, + 53.0218087 + ], + [ + 6.560159, + 53.0218087 + ], + [ + 6.560159, + 53.0218087 + ], + [ + 6.560159, + 53.0218087 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T13:11:13Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 7, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112868634, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2261381, + 51.215136 + ], + [ + 3.2263112, + 51.215136 + ], + [ + 3.2263112, + 51.2161355 + ], + [ + 3.2261381, + 51.2161355 + ], + [ + 3.2261381, + 51.215136 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T10:26:11Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 1.73013449999852e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "grb", + "answer": 4, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112868128, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.2773693, + 51.1411478 + ], + [ + 6.2773693, + 51.1411478 + ], + [ + 6.2773693, + 51.1411478 + ], + [ + 6.2773693, + 51.1411478 + ], + [ + 6.2773693, + 51.1411478 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "tux67", + "uid": "112465", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T10:04:49Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "answer": 3, + "imagery": "osm", + "language": "de" + } + } + }, + { + "id": 112867160, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.1004699, + 52.0988985 + ], + [ + 5.1011539, + 52.0988985 + ], + [ + 5.1011539, + 52.0992181 + ], + [ + 5.1004699, + 52.0992181 + ], + [ + 5.1004699, + 52.0988985 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Koen Rijnsent", + "uid": "4569696", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T09:30:44Z", + "reviewed_features": [], + "create": 4, + "modify": 9, + "delete": 0, + "area": 2.18606400002996e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "artwork", + "answer": 8, + "create": 4, + "imagery": "osm", + "language": "en", + "add-image": 4 + } + } + }, + { + "id": 112866456, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.0910025, + 52.1017588 + ], + [ + 5.0954407, + 52.1017588 + ], + [ + 5.0954407, + 52.1030985 + ], + [ + 5.0910025, + 52.1030985 + ], + [ + 5.0910025, + 52.1017588 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Koen Rijnsent", + "uid": "4569696", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T09:01:48Z", + "reviewed_features": [], + "create": 2, + "modify": 5, + "delete": 0, + "area": 0.00000594585654001165, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "answer": 6, + "create": 2, + "imagery": "osm", + "language": "en", + "add-image": 2 + } + } + }, + { + "id": 112865466, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.8729827, + 45.4682787 + ], + [ + 7.8800102, + 45.4682787 + ], + [ + 7.8800102, + 45.4707397 + ], + [ + 7.8729827, + 45.4707397 + ], + [ + 7.8729827, + 45.4682787 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Valerio_Bozzolan", + "uid": "1875845", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #shops", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T08:16:34Z", + "reviewed_features": [], + "create": 0, + "modify": 8, + "delete": 0, + "area": 0.0000172946775000279, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "shops", + "answer": 8, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112864730, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.1485138, + 55.6929421 + ], + [ + 9.1485138, + 55.6929421 + ], + [ + 9.1485138, + 55.6929421 + ], + [ + 9.1485138, + 55.6929421 + ], + [ + 9.1485138, + 55.6929421 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-23T07:40:59Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 2, + "create": 1, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112861808, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -76.6471508, + 39.3191368 + ], + [ + -76.6471508, + 39.3191368 + ], + [ + -76.6471508, + 39.3191368 + ], + [ + -76.6471508, + 39.3191368 + ], + [ + -76.6471508, + 39.3191368 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "elipousson", + "uid": "137861", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T03:41:52Z", + "reviewed_features": [], + "create": 1, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "artwork", + "answer": 3, + "create": 1, + "imagery": "geodata.md.gov-MD_SixInchImagery", + "language": "en" + } + } + }, + { + "id": 112861396, + "type": "Feature", + "geometry": null, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "SherbetS", + "uid": "12163682", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #test", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T02:52:43Z", + "reviewed_features": [], + "create": 0, + "modify": 0, + "delete": 0, + "area": null, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "test", + "answer": 1, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112861395, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -85.4725958, + 32.5885088 + ], + [ + -85.4725958, + 32.5885088 + ], + [ + -85.4725958, + 32.5885088 + ], + [ + -85.4725958, + 32.5885088 + ], + [ + -85.4725958, + 32.5885088 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "SherbetS", + "uid": "12163682", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-23T02:52:43Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "en", + "add-image": 1 + } + } + }, + { + "id": 112856442, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.1164614, + 52.8052704 + ], + [ + -2.1164555, + 52.8052704 + ], + [ + -2.1164555, + 52.8052925 + ], + [ + -2.1164614, + 52.8052925 + ], + [ + -2.1164614, + 52.8052704 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T20:48:35Z", + "reviewed_features": [], + "create": 1, + "modify": 3, + "delete": 0, + "area": 1.3039000001708e-10, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 5, + "create": 1, + "imagery": "EsriWorldImageryClarity", + "language": "nl" + } + } + }, + { + "id": 112856106, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.224992, + 51.212664 + ], + [ + 3.2353575, + 51.212664 + ], + [ + 3.2353575, + 51.2143991 + ], + [ + 3.224992, + 51.2143991 + ], + [ + 3.224992, + 51.212664 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T20:38:04Z", + "reviewed_features": [], + "create": 0, + "modify": 14, + "delete": 0, + "area": 0.0000179851790500498, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "grb", + "answer": 15, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112856017, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2376945, + 51.2149454 + ], + [ + 3.2376945, + 51.2149454 + ], + [ + 3.2376945, + 51.2149454 + ], + [ + 3.2376945, + 51.2149454 + ], + [ + 3.2376945, + 51.2149454 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #surveillance", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T20:35:19Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "surveillance", + "answer": 3, + "create": 2, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112852101, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5488267, + 53.0015413 + ], + [ + 6.5491077, + 53.0015413 + ], + [ + 6.5491077, + 53.0015956 + ], + [ + 6.5488267, + 53.0015956 + ], + [ + 6.5488267, + 53.0015413 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T18:58:11Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 1.52583000006211e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 1, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112849606, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2226879, + 51.2133141 + ], + [ + 3.2259155, + 51.2133141 + ], + [ + 3.2259155, + 51.2150424 + ], + [ + 3.2226879, + 51.2150424 + ], + [ + 3.2226879, + 51.2133141 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T17:42:39Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.00000557826108001154, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "grb", + "answer": 2, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112847851, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2399328, + 51.2127007 + ], + [ + 3.2399328, + 51.2127007 + ], + [ + 3.2399328, + 51.2127007 + ], + [ + 3.2399328, + 51.2127007 + ], + [ + 3.2399328, + 51.2127007 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T17:01:27Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 1, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112844464, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2398742, + 51.2126842 + ], + [ + 3.2399395, + 51.2126842 + ], + [ + 3.2399395, + 51.2127761 + ], + [ + 3.2398742, + 51.2127761 + ], + [ + 3.2398742, + 51.2126842 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T15:42:00Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 6.00107000008186e-9, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "move": 3, + "theme": "cyclofix", + "answer": 2, + "imagery": "CartoDB.Voyager", + "language": "en", + "add-image": 1, + "move:node/4784533428": "The location of this object is inaccurate and should be moved a few meter", + "move:node/8202442918": "The location of this object is inaccurate and should be moved a few meter" + } + } + }, + { + "id": 112843053, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2315469, + 51.2137869 + ], + [ + 3.2372412, + 51.2137869 + ], + [ + 3.2372412, + 51.2147707 + ], + [ + 3.2315469, + 51.2147707 + ], + [ + 3.2315469, + 51.2137869 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T15:12:49Z", + "reviewed_features": [], + "create": 1, + "modify": 2, + "delete": 0, + "area": 0.00000560205234000119, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "artwork", + "answer": 2, + "create": 1, + "imagery": "osm", + "language": "en", + "add-image": 2 + } + } + }, + { + "id": 112840556, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.2369476, + 50.7326341 + ], + [ + 4.2369476, + 50.7326341 + ], + [ + 4.2369476, + 50.7326341 + ], + [ + 4.2369476, + 50.7326341 + ], + [ + 4.2369476, + 50.7326341 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T14:19:21Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "answer": 1, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112840419, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 14.6420013, + 52.0122072 + ], + [ + 14.6420013, + 52.0122072 + ], + [ + 14.6420013, + 52.0122072 + ], + [ + 14.6420013, + 52.0122072 + ], + [ + 14.6420013, + 52.0122072 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "PaulSembten", + "uid": "13999064", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T14:15:56Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112838739, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.1687169, + 50.6640325 + ], + [ + 6.191557, + 50.6640325 + ], + [ + 6.191557, + 50.6763457 + ], + [ + 6.1687169, + 50.6763457 + ], + [ + 6.1687169, + 50.6640325 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Fasse", + "uid": "302717", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #openwindpowermap", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T13:38:30Z", + "reviewed_features": [], + "create": 0, + "modify": 10, + "delete": 0, + "area": 0.000281234719320037, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "openwindpowermap", + "answer": 21, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112838646, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2516995, + 41.4479352 + ], + [ + 2.2516995, + 41.4479352 + ], + [ + 2.2516995, + 41.4479352 + ], + [ + 2.2516995, + 41.4479352 + ], + [ + 2.2516995, + 41.4479352 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Driesvr", + "uid": "4757701", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-22T13:35:54Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 4, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112838009, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.1834043, + 50.6375631 + ], + [ + 6.2011342, + 50.6375631 + ], + [ + 6.2011342, + 50.657581 + ], + [ + 6.1834043, + 50.657581 + ], + [ + 6.1834043, + 50.6375631 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Fasse", + "uid": "302717", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #food", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T13:17:04Z", + "reviewed_features": [], + "create": 0, + "modify": 17, + "delete": 0, + "area": 0.000354915365209984, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "food", + "answer": 21, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112837641, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.1838972, + 50.6458567 + ], + [ + 6.1858414, + 50.6458567 + ], + [ + 6.1858414, + 50.6472039 + ], + [ + 6.1838972, + 50.6472039 + ], + [ + 6.1838972, + 50.6458567 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Fasse", + "uid": "302717", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T13:07:24Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0.00000261922623999657, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "answer": 7, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112837241, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.1839155, + 50.6357237 + ], + [ + 6.2081823, + 50.6357237 + ], + [ + 6.2081823, + 50.6478957 + ], + [ + 6.1839155, + 50.6478957 + ], + [ + 6.1839155, + 50.6357237 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Fasse", + "uid": "302717", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T12:56:13Z", + "reviewed_features": [], + "create": 0, + "modify": 12, + "delete": 0, + "area": 0.000295375489599985, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 18, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112837065, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.1838941, + 50.6471167 + ], + [ + 6.1838941, + 50.6471167 + ], + [ + 6.1838941, + 50.6471167 + ], + [ + 6.1838941, + 50.6471167 + ], + [ + 6.1838941, + 50.6471167 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Fasse", + "uid": "302717", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #toilets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T12:51:58Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "toilets", + "answer": 4, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112831683, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.1450558, + 51.1829237 + ], + [ + 4.178697, + 51.1829237 + ], + [ + 4.178697, + 51.195708 + ], + [ + 4.1450558, + 51.195708 + ], + [ + 4.1450558, + 51.1829237 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Rembrandt De Vlaeminck", + "uid": "504998", + "editor": "MapComplete 0.0.8f", + "comment": "Adding data with #MapComplete for theme #buurtnatuur", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T10:37:28Z", + "reviewed_features": [], + "create": 0, + "modify": 16, + "delete": 0, + "area": 0.000430079193159994, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "theme": "buurtnatuur", + "theme-creator": "Pieter Vander Vennet" + } + } + }, + { + "id": 112828266, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2376713, + 41.4647223 + ], + [ + 2.2378787, + 41.4647223 + ], + [ + 2.2378787, + 41.4648308 + ], + [ + 2.2376713, + 41.4648308 + ], + [ + 2.2376713, + 41.4647223 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "PabloDíaz", + "uid": "14309824", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #https://llefia.org/arbres/mapcomplete1.json", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T09:12:04Z", + "reviewed_features": [], + "create": 5, + "modify": 5, + "delete": 0, + "area": 2.25029000006432e-8, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://llefia.org/arbres/mapcomplete1.json", + "answer": 6, + "create": 5, + "imagery": "HDM_HOT", + "language": "ca" + } + } + }, + { + "id": 112827018, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.127738, + 51.1055311 + ], + [ + 4.6483799, + 51.1055311 + ], + [ + 4.6483799, + 51.2199188 + ], + [ + 4.127738, + 51.2199188 + ], + [ + 4.127738, + 51.1055311 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Rembrandt De Vlaeminck", + "uid": "504998", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #nature", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T08:43:02Z", + "reviewed_features": [], + "create": 0, + "modify": 11, + "delete": 0, + "area": 0.0595550294646309, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "nature", + "answer": 17, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112825777, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.1567132, + 51.1771083 + ], + [ + 4.1567132, + 51.1771083 + ], + [ + 4.1567132, + 51.1771083 + ], + [ + 4.1567132, + 51.1771083 + ], + [ + 4.1567132, + 51.1771083 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Rembrandt De Vlaeminck", + "uid": "504998", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #playgrounds", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-22T08:09:56Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "playgrounds", + "answer": 1, + "create": 1, + "imagery": "AGIV", + "language": "nl" + } + } + }, + { + "id": 112805789, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 14.6371402, + 52.0175094 + ], + [ + 14.6371402, + 52.0175094 + ], + [ + 14.6371402, + 52.0175094 + ], + [ + 14.6371402, + 52.0175094 + ], + [ + 14.6371402, + 52.0175094 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "PaulSembten", + "uid": "13999064", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-21T18:46:45Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112799378, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2042052, + 51.1989816 + ], + [ + 3.2071848, + 51.1989816 + ], + [ + 3.2071848, + 51.2002878 + ], + [ + 3.2042052, + 51.2002878 + ], + [ + 3.2042052, + 51.1989816 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-21T16:05:07Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0.00000389195351998423, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "etymology", + "answer": 5, + "imagery": "osm", + "language": "nl", + "add-image": 1 + } + } + }, + { + "id": 112796853, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2013154, + 51.1990973 + ], + [ + 3.2089586, + 51.1990973 + ], + [ + 3.2089586, + 51.2024421 + ], + [ + 3.2013154, + 51.2024421 + ], + [ + 3.2013154, + 51.1990973 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-21T15:01:11Z", + "reviewed_features": [], + "create": 0, + "modify": 11, + "delete": 0, + "area": 0.0000255649753600052, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "grb", + "answer": 18, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112788625, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.2426755, + 50.7355206 + ], + [ + 4.2560441, + 50.7355206 + ], + [ + 4.2560441, + 50.7406058 + ], + [ + 4.2426755, + 50.7406058 + ], + [ + 4.2426755, + 50.7355206 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-21T11:45:46Z", + "reviewed_features": [], + "create": 2, + "modify": 4, + "delete": 0, + "area": 0.0000679820047199543, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "answer": 10, + "create": 2, + "imagery": "CartoDB.Voyager", + "language": "nl", + "add-image": 1 + } + } + }, + { + "id": 112777915, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.3294368, + 50.9907606 + ], + [ + 3.3294368, + 50.9907606 + ], + [ + 3.3294368, + 50.9907606 + ], + [ + 3.3294368, + 50.9907606 + ], + [ + 3.3294368, + 50.9907606 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "bombibom", + "uid": "14326663", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #surveillance", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-21T07:54:12Z", + "reviewed_features": [], + "create": 1, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "surveillance", + "answer": 2, + "create": 1, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112769664, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 121.0318265, + 14.6058164 + ], + [ + 121.0319555, + 14.6058164 + ], + [ + 121.0319555, + 14.6059818 + ], + [ + 121.0318265, + 14.6059818 + ], + [ + 121.0318265, + 14.6058164 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "GOwin", + "uid": "1041828", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #postboxes", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-21T03:39:44Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 2.13366000002175e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "postboxes", + "answer": 1, + "imagery": "CartoDB.Voyager", + "language": "en" + } + } + }, + { + "id": 112768775, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.8219139, + 47.6452408 + ], + [ + 7.8219139, + 47.6452408 + ], + [ + 7.8219139, + 47.6452408 + ], + [ + 7.8219139, + 47.6452408 + ], + [ + 7.8219139, + 47.6452408 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "tbkrtz", + "uid": "8453504", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-21T02:41:52Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "answer": 4, + "imagery": "CartoDB.Voyager", + "language": "de" + } + } + }, + { + "id": 112768712, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.8237334, + 47.6456871 + ], + [ + 7.8237334, + 47.6456871 + ], + [ + 7.8237334, + 47.6456871 + ], + [ + 7.8237334, + 47.6456871 + ], + [ + 7.8237334, + 47.6456871 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "tbkrtz", + "uid": "8453504", + "editor": "MapComplete 0.11.0", + "comment": "Adding data with #MapComplete for theme #cycle_infra", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-21T02:37:11Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cycle_infra", + "answer": 1, + "imagery": "CartoDB.Voyager", + "language": "de" + } + } + }, + { + "id": 112763424, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.8224244, + 47.6483941 + ], + [ + 7.8233899, + 47.6483941 + ], + [ + 7.8233899, + 47.6488233 + ], + [ + 7.8224244, + 47.6488233 + ], + [ + 7.8224244, + 47.6483941 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "tbkrtz", + "uid": "8453504", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T21:08:27Z", + "reviewed_features": [], + "create": 0, + "modify": 6, + "delete": 0, + "area": 4.14392599999057e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112761963, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5595136, + 53.0192929 + ], + [ + 6.5625272, + 53.0192929 + ], + [ + 6.5625272, + 53.0210558 + ], + [ + 6.5595136, + 53.0210558 + ], + [ + 6.5595136, + 53.0192929 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T20:25:37Z", + "reviewed_features": [], + "create": 1, + "modify": 35, + "delete": 0, + "area": 0.00000531267543998709, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 88, + "create": 1, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112757603, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 12.8910801, + 53.1005661 + ], + [ + 12.8910801, + 53.1005661 + ], + [ + 12.8910801, + 53.1005661 + ], + [ + 12.8910801, + 53.1005661 + ], + [ + 12.8910801, + 53.1005661 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "hfs", + "uid": "9607", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #food", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-20T18:31:21Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "food", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112756229, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.2392313, + 50.7322621 + ], + [ + 4.2392313, + 50.7322621 + ], + [ + 4.2392313, + 50.7322621 + ], + [ + 4.2392313, + 50.7322621 + ], + [ + 4.2392313, + 50.7322621 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T17:55:25Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "nl", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112753161, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.57581, + 53.2152218 + ], + [ + 6.6148031, + 53.2152218 + ], + [ + 6.6148031, + 53.2244879 + ], + [ + 6.57581, + 53.2244879 + ], + [ + 6.57581, + 53.2152218 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T16:44:20Z", + "reviewed_features": [], + "create": 0, + "modify": 124, + "delete": 0, + "area": 0.000361313963909915, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "etymology", + "answer": 253, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112752015, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5131358, + 52.975784 + ], + [ + 6.5812529, + 52.975784 + ], + [ + 6.5812529, + 52.9940098 + ], + [ + 6.5131358, + 52.9940098 + ], + [ + 6.5131358, + 52.975784 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T16:17:38Z", + "reviewed_features": [], + "create": 0, + "modify": 83, + "delete": 0, + "area": 0.00124148864118023, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "etymology", + "answer": 155, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112748629, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.7673063, + 50.8715807 + ], + [ + 4.8836353, + 50.8715807 + ], + [ + 4.8836353, + 50.895814 + ], + [ + 4.7673063, + 50.895814 + ], + [ + 4.7673063, + 50.8715807 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Koenraad Van Coppenolle", + "uid": "12352906", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-20T15:01:06Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.00281903555569985, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "imagery": "CartoDB.Voyager", + "language": "nl" + } + } + }, + { + "id": 112748388, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5702664, + 53.0177802 + ], + [ + 6.5718454, + 53.0177802 + ], + [ + 6.5718454, + 53.0210592 + ], + [ + 6.5702664, + 53.0210592 + ], + [ + 6.5702664, + 53.0177802 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T14:55:51Z", + "reviewed_features": [], + "create": 0, + "modify": 83, + "delete": 0, + "area": 0.0000051775410000084, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 205, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112747371, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -9.1238252, + 53.2601483 + ], + [ + -9.1038125, + 53.2601483 + ], + [ + -9.1038125, + 53.2692424 + ], + [ + -9.1238252, + 53.2692424 + ], + [ + -9.1238252, + 53.2601483 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Cmap99", + "uid": "13524250", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cycle_infra", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-20T14:31:51Z", + "reviewed_features": [], + "create": 0, + "modify": 13, + "delete": 0, + "area": 0.00018199749507012, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cycle_infra", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112745665, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.2694622, + 47.7112794 + ], + [ + 9.2694622, + 47.7112794 + ], + [ + 9.2694622, + 47.7112794 + ], + [ + 9.2694622, + 47.7112794 + ], + [ + 9.2694622, + 47.7112794 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-20T13:50:23Z", + "reviewed_features": [], + "create": 1, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112745041, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2202656, + 51.2152235 + ], + [ + 3.2212079, + 51.2152235 + ], + [ + 3.2212079, + 51.2181563 + ], + [ + 3.2202656, + 51.2181563 + ], + [ + 3.2202656, + 51.2152235 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-alpha-2", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T13:36:47Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0.00000276357743999712, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/alpha/", + "theme": "etymology", + "answer": 3, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112740347, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5804162, + 53.2129721 + ], + [ + 6.6004165, + 53.2129721 + ], + [ + 6.6004165, + 53.2180528 + ], + [ + 6.5804162, + 53.2180528 + ], + [ + 6.5804162, + 53.2129721 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T11:40:55Z", + "reviewed_features": [], + "create": 0, + "modify": 73, + "delete": 0, + "area": 0.00010161552421001, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "etymology", + "answer": 130, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112738677, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2084243, + 51.2081959 + ], + [ + 3.2338334, + 51.2081959 + ], + [ + 3.2338334, + 51.218774 + ], + [ + 3.2084243, + 51.218774 + ], + [ + 3.2084243, + 51.2081959 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T11:00:23Z", + "reviewed_features": [], + "create": 0, + "modify": 16, + "delete": 0, + "area": 0.000268780000710092, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "grb", + "answer": 18, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112738403, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2143236, + 51.2098348 + ], + [ + 3.2382683, + 51.2098348 + ], + [ + 3.2382683, + 51.2156301 + ], + [ + 3.2143236, + 51.2156301 + ], + [ + 3.2143236, + 51.2098348 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-alpha-2", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T10:53:26Z", + "reviewed_features": [], + "create": 0, + "modify": 7, + "delete": 0, + "area": 0.000138766719909892, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/alpha/", + "theme": "etymology", + "answer": 8, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112733217, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Frans_Napaters", + "uid": "3574538", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 1, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T08:53:05Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112729830, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.7175022, + 51.0432208 + ], + [ + 3.7178813, + 51.0432208 + ], + [ + 3.7178813, + 51.043388 + ], + [ + 3.7175022, + 51.043388 + ], + [ + 3.7175022, + 51.0432208 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T07:28:32Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 6.3385519999965e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "nl", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112729580, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.7110539, + 51.0423597 + ], + [ + 3.7110539, + 51.0423597 + ], + [ + 3.7110539, + 51.0423597 + ], + [ + 3.7110539, + 51.0423597 + ], + [ + 3.7110539, + 51.0423597 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T07:21:42Z", + "reviewed_features": [], + "create": 1, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "imagery": "CartoDB.Voyager", + "language": "nl" + } + } + }, + { + "id": 112728248, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.5757919, + 50.6486646 + ], + [ + 5.5757919, + 50.6486646 + ], + [ + 5.5757919, + 50.6486646 + ], + [ + 5.5757919, + 50.6486646 + ], + [ + 5.5757919, + 50.6486646 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "plicploc", + "uid": "75871", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-20T06:47:29Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112718253, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.5874942, + 47.6678007 + ], + [ + 9.5900852, + 47.6678007 + ], + [ + 9.5900852, + 47.6704703 + ], + [ + 9.5874942, + 47.6704703 + ], + [ + 9.5874942, + 47.6678007 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T21:32:56Z", + "reviewed_features": [], + "create": 2, + "modify": 7, + "delete": 0, + "area": 0.00000691693359999474, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112712386, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2185596, + 51.2131485 + ], + [ + 3.2185596, + 51.2131485 + ], + [ + 3.2185596, + 51.2131485 + ], + [ + 3.2185596, + 51.2131485 + ], + [ + 3.2185596, + 51.2131485 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T18:37:44Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "nl", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112710590, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2308717, + 41.4470097 + ], + [ + 2.232173, + 41.4470097 + ], + [ + 2.232173, + 41.4472648 + ], + [ + 2.2308717, + 41.4472648 + ], + [ + 2.2308717, + 41.4470097 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Maribelens", + "uid": "13480216", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #https://llefia.org/arbres/mapcomplete1.json", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T17:55:41Z", + "reviewed_features": [], + "create": 1, + "modify": 15, + "delete": 0, + "area": 3.31961629995937e-7, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://llefia.org/arbres/mapcomplete1.json", + "imagery": "HDM_HOT", + "language": "ca" + } + } + }, + { + "id": 112710210, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2157495, + 51.2089829 + ], + [ + 3.2167779, + 51.2089829 + ], + [ + 3.2167779, + 51.2092374 + ], + [ + 3.2157495, + 51.2092374 + ], + [ + 3.2157495, + 51.2089829 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T17:47:14Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 2.61727799996861e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "etymology", + "answer": 3, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112709992, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2147004, + 51.2078665 + ], + [ + 3.2197883, + 51.2078665 + ], + [ + 3.2197883, + 51.21868 + ], + [ + 3.2147004, + 51.21868 + ], + [ + 3.2147004, + 51.2078665 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #grb", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T17:42:51Z", + "reviewed_features": [], + "create": 0, + "modify": 47, + "delete": 0, + "area": 0.0000550180066499869, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "grb", + "answer": 55, + "imagery": "AGIVFlandersGRB", + "language": "nl" + } + } + }, + { + "id": 112705847, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.6043359, + 53.2170286 + ], + [ + 6.6043359, + 53.2170286 + ], + [ + 6.6043359, + 53.2170286 + ], + [ + 6.6043359, + 53.2170286 + ], + [ + 6.6043359, + 53.2170286 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T16:06:09Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 3, + "create": 1, + "imagery": "Actueel_ortho25_WMS", + "language": "nl" + } + } + }, + { + "id": 112701458, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5562924, + 53.0150928 + ], + [ + 6.5576007, + 53.0150928 + ], + [ + 6.5576007, + 53.0161698 + ], + [ + 6.5562924, + 53.0161698 + ], + [ + 6.5562924, + 53.0150928 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T14:37:27Z", + "reviewed_features": [], + "create": 0, + "modify": 15, + "delete": 0, + "area": 0.0000014090391000027, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 35, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112699968, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ], + [ + 4.8807272, + 51.0598072 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Frans_Napaters", + "uid": "3574538", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T14:03:03Z", + "reviewed_features": [], + "create": 1, + "modify": 6, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112697990, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.2358518, + 50.7362727 + ], + [ + 4.243543, + 50.7362727 + ], + [ + 4.243543, + 50.7387704 + ], + [ + 4.2358518, + 50.7387704 + ], + [ + 4.2358518, + 50.7362727 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #toerisme_vlaanderen", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T13:16:42Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0.0000192103102399944, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "toerisme_vlaanderen", + "answer": 11, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112695953, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2228096, + 51.2147707 + ], + [ + 3.2372412, + 51.2147707 + ], + [ + 3.2372412, + 51.2157972 + ], + [ + 3.2228096, + 51.2157972 + ], + [ + 3.2228096, + 51.2147707 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T12:26:15Z", + "reviewed_features": [], + "create": 2, + "modify": 3, + "delete": 0, + "area": 0.0000148140373999237, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "artwork", + "answer": 2, + "create": 2, + "imagery": "osm", + "language": "nl", + "add-image": 3 + } + } + }, + { + "id": 112694872, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.5722951, + 50.6552402 + ], + [ + 5.5722951, + 50.6552402 + ], + [ + 5.5722951, + 50.6552402 + ], + [ + 5.5722951, + 50.6552402 + ], + [ + 5.5722951, + 50.6552402 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "plicploc", + "uid": "75871", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T11:58:34Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112694402, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.2906018, + 51.4654127 + ], + [ + 7.307979, + 51.4654127 + ], + [ + 7.307979, + 51.4713082 + ], + [ + 7.2906018, + 51.4713082 + ], + [ + 7.2906018, + 51.4654127 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "BS97n", + "uid": "2386081", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #personal", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T11:47:53Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0.000102447282600017, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "personal", + "imagery": "Metropole_Ruhr_RVR-DOP10", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112693435, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5575847, + 53.0159111 + ], + [ + 6.5724179, + 53.0159111 + ], + [ + 6.5724179, + 53.0265072 + ], + [ + 6.5575847, + 53.0265072 + ], + [ + 6.5575847, + 53.0159111 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.11.0-rc-1", + "comment": "Adding data with #MapComplete for theme #street_lighting", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-19T11:29:13Z", + "reviewed_features": [], + "create": 3, + "modify": 67, + "delete": 0, + "area": 0.000157174070520011, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/theme/street_lighting/", + "theme": "street_lighting", + "answer": 229, + "create": 3, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112690492, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -9.0755173, + 53.2752413 + ], + [ + -9.0755173, + 53.2752413 + ], + [ + -9.0755173, + 53.2752413 + ], + [ + -9.0755173, + 53.2752413 + ], + [ + -9.0755173, + 53.2752413 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Cmap99", + "uid": "13524250", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T10:20:48Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112690102, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -9.1028462, + 53.2630503 + ], + [ + -9.074794, + 53.2630503 + ], + [ + -9.074794, + 53.2764646 + ], + [ + -9.1028462, + 53.2764646 + ], + [ + -9.1028462, + 53.2630503 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Cmap99", + "uid": "13524250", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #food", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T10:11:20Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 0.000376300626459817, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "food", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112689371, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -9.0455576, + 53.2748582 + ], + [ + -9.0455576, + 53.2748582 + ], + [ + -9.0455576, + 53.2748582 + ], + [ + -9.0455576, + 53.2748582 + ], + [ + -9.0455576, + 53.2748582 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Cmap99", + "uid": "13524250", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T09:53:53Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112685292, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.2694676, + 45.5034727 + ], + [ + 9.2694676, + 45.5034727 + ], + [ + 9.2694676, + 45.5034727 + ], + [ + 9.2694676, + 45.5034727 + ], + [ + 9.2694676, + 45.5034727 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "livmilan", + "uid": "712033", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #shops", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-19T08:22:06Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "shops", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112673477, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2137117, + 51.2153253 + ], + [ + 3.2225958, + 51.2153253 + ], + [ + 3.2225958, + 51.2199112 + ], + [ + 3.2137117, + 51.2199112 + ], + [ + 3.2137117, + 51.2153253 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cycle_infra", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T23:31:59Z", + "reviewed_features": [], + "create": 0, + "modify": 7, + "delete": 0, + "area": 0.0000407415941899557, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cycle_infra", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112670419, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -75.5066331, + 5.0159707 + ], + [ + -75.4994317, + 5.0159707 + ], + [ + -75.4994317, + 5.0209574 + ], + [ + -75.5066331, + 5.0209574 + ], + [ + -75.5066331, + 5.0159707 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "afgb1977", + "uid": "10218404", + "editor": "MapComplete 0.0.8f", + "comment": "Adding data with #MapComplete for theme #buurtnatuur", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T21:01:15Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0.0000359112213800036, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "theme": "buurtnatuur", + "theme-creator": "Pieter Vander Vennet" + } + } + }, + { + "id": 112668948, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5719056, + 52.9881679 + ], + [ + 6.5719056, + 52.9881679 + ], + [ + 6.5719056, + 52.9881679 + ], + [ + 6.5719056, + 52.9881679 + ], + [ + 6.5719056, + 52.9881679 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #charging_stations", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T20:14:38Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "charging_stations", + "imagery": "Actueel_ortho25_WMS", + "language": "nl" + } + } + }, + { + "id": 112668843, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 6.5606219, + 53.0192702 + ], + [ + 6.5617339, + 53.0192702 + ], + [ + 6.5617339, + 53.0196606 + ], + [ + 6.5606219, + 53.0196606 + ], + [ + 6.5606219, + 53.0192702 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Robin van der Linde", + "uid": "5093765", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #https://gist.githubusercontent.com/RobinLinde/d22223ebe86469b0ff08e7f308ab109c/raw/774eb9ea5e8ad150b1d01868b4d51f5ca219101e/street_lighting.json", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T20:11:50Z", + "reviewed_features": [], + "create": 0, + "modify": 12, + "delete": 0, + "area": 4.34124800000632e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://gist.githubusercontent.com/RobinLinde/d22223ebe86469b0ff08e7f308ab109c/raw/774eb9ea5e8ad150b1d01868b4d51f5ca219101e/street_lighting.json", + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112668565, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2153406, + 51.2156295 + ], + [ + 3.2154107, + 51.2156295 + ], + [ + 3.2154107, + 51.2156721 + ], + [ + 3.2153406, + 51.2156721 + ], + [ + 3.2153406, + 51.2156295 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T20:03:20Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 2.98626000002504e-9, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "127.0.0.1:1234", + "move": 2, + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "en", + "move:node/4796376181": "The location of this object is inaccurate and should be moved a few meter", + "move:node/8149481171": "The location of this object is inaccurate and should be moved a few meter" + } + } + }, + { + "id": 112667042, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 11.916905, + 57.6772701 + ], + [ + 11.9462415, + 57.6772701 + ], + [ + 11.9462415, + 57.6967646 + ], + [ + 11.916905, + 57.6967646 + ], + [ + 11.916905, + 57.6772701 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Tor H", + "uid": "6364105", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #sport_pitches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T19:26:42Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 0.000571900399250001, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "sport_pitches", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112663580, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 14.4781727, + 51.9319697 + ], + [ + 14.481883, + 51.9319697 + ], + [ + 14.481883, + 51.935301 + ], + [ + 14.4781727, + 51.935301 + ], + [ + 14.4781727, + 51.9319697 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Mitch85", + "uid": "4907923", + "editor": "MapComplete 0.7.2l", + "comment": "Adding data with #MapComplete for theme #waldbrand", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T17:42:40Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.0000123601223899967, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "waldbrand-app.de", + "theme": "waldbrand", + "imagery": "osm", + "language": "de", + "theme-creator": "Sebastian Kürten" + } + } + }, + { + "id": 112658974, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.2655221, + 47.6993178 + ], + [ + 9.2967618, + 47.6993178 + ], + [ + 9.2967618, + 47.7218128 + ], + [ + 9.2655221, + 47.7218128 + ], + [ + 9.2655221, + 47.6993178 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T15:48:08Z", + "reviewed_features": [], + "create": 6, + "modify": 21, + "delete": 0, + "area": 0.000702737051499987, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112658231, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.2660263, + 47.6875214 + ], + [ + 9.2821142, + 47.6875214 + ], + [ + 9.2821142, + 47.6966894 + ], + [ + 9.2660263, + 47.6966894 + ], + [ + 9.2660263, + 47.6875214 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T15:30:52Z", + "reviewed_features": [], + "create": 4, + "modify": 27, + "delete": 0, + "area": 0.00014749386719993, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112657372, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.0720514, + 50.8425665 + ], + [ + 4.0720514, + 50.8425665 + ], + [ + 4.0720514, + 50.8425665 + ], + [ + 4.0720514, + 50.8425665 + ], + [ + 4.0720514, + 50.8425665 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "joost schouppe", + "uid": "67832", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #campersite", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T15:10:29Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "campersite", + "imagery": "osm", + "language": "nl", + "theme-creator": "joost schouppe" + } + } + }, + { + "id": 112654375, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2027742, + 51.2162595 + ], + [ + 3.2036648, + 51.2162595 + ], + [ + 3.2036648, + 51.2170519 + ], + [ + 3.2027742, + 51.2170519 + ], + [ + 3.2027742, + 51.2162595 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #etymology", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T13:51:02Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 7.05711440001478e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "127.0.0.1:1234", + "theme": "etymology", + "answer": 3, + "imagery": "osm", + "language": "nl" + } + } + }, + { + "id": 112652820, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.2723456, + 47.6947613 + ], + [ + 9.2723456, + 47.6947613 + ], + [ + 9.2723456, + 47.6947613 + ], + [ + 9.2723456, + 47.6947613 + ], + [ + 9.2723456, + 47.6947613 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "sualko", + "uid": "11086971", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T13:10:14Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112649426, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.7449888, + 50.2991705 + ], + [ + 2.7449888, + 50.2991705 + ], + [ + 2.7449888, + 50.2991705 + ], + [ + 2.7449888, + 50.2991705 + ], + [ + 2.7449888, + 50.2991705 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Max Kritic", + "uid": "414659", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #hailhydrant", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T11:46:11Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "hailhydrant", + "imagery": "HDM_HOT", + "language": "fr", + "theme-creator": "Erwin Olario" + } + } + }, + { + "id": 112641505, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.0869942, + 45.4881026 + ], + [ + 9.2262606, + 45.4881026 + ], + [ + 9.2262606, + 45.6564621 + ], + [ + 9.0869942, + 45.6564621 + ], + [ + 9.0869942, + 45.4881026 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Valerio_Bozzolan", + "uid": "1875845", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #vets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T08:31:12Z", + "reviewed_features": [], + "create": 0, + "modify": 7, + "delete": 0, + "area": 0.0234468214708002, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "vets", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112638273, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.7411764, + 45.1478361 + ], + [ + 5.7496431, + 45.1478361 + ], + [ + 5.7496431, + 45.1525931 + ], + [ + 5.7411764, + 45.1525931 + ], + [ + 5.7411764, + 45.1478361 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "chimel38", + "uid": "148831", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #sport_pitches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T07:05:51Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.0000402760918999846, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "sport_pitches", + "imagery": "osm", + "language": "fr" + } + } + }, + { + "id": 112637945, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.616621, + 44.827888 + ], + [ + 5.616621, + 44.827888 + ], + [ + 5.616621, + 44.827888 + ], + [ + 5.616621, + 44.827888 + ], + [ + 5.616621, + 44.827888 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "chimel38", + "uid": "148831", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #hailhydrant", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-18T06:57:06Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "hailhydrant", + "imagery": "HDM_HOT", + "language": "en", + "theme-creator": "Erwin Olario" + } + } + }, + { + "id": 112637933, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.2266209, + 49.0068452 + ], + [ + 8.4461766, + 49.0068452 + ], + [ + 8.4461766, + 50.0194721 + ], + [ + 8.2266209, + 50.0194721 + ], + [ + 8.2266209, + 49.0068452 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "mfbehrens99", + "uid": "9645335", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #climbing", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T06:56:43Z", + "reviewed_features": [], + "create": 0, + "modify": 8, + "delete": 0, + "area": 0.222328007868329, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "climbing", + "imagery": "osm", + "language": "en", + "theme-creator": "Christian Neumann " + } + } + }, + { + "id": 112636966, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.1226001, + 52.078219 + ], + [ + 5.1227028, + 52.078219 + ], + [ + 5.1227028, + 52.0784914 + ], + [ + 5.1226001, + 52.0784914 + ], + [ + 5.1226001, + 52.078219 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Koen Rijnsent", + "uid": "4569696", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T06:30:16Z", + "reviewed_features": [], + "create": 3, + "modify": 4, + "delete": 0, + "area": 2.79754800000698e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "artwork", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112635876, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -3.3341011, + 52.275169 + ], + [ + -3.3341011, + 52.275169 + ], + [ + -3.3341011, + 52.275169 + ], + [ + -3.3341011, + 52.275169 + ], + [ + -3.3341011, + 52.275169 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Russ McD", + "uid": "346601", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #toilets", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T05:58:47Z", + "reviewed_features": [], + "create": 1, + "modify": 0, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "toilets", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112630581, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.3713472, + 47.6895039 + ], + [ + -122.371347, + 47.6895039 + ], + [ + -122.371347, + 47.6895039 + ], + [ + -122.3713472, + 47.6895039 + ], + [ + -122.3713472, + 47.6895039 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Wim L", + "uid": "223681", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #bookcases", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-18T00:15:43Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "bookcases", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112628013, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.4455502, + 49.0258263 + ], + [ + 8.4461766, + 49.0258263 + ], + [ + 8.4461766, + 49.0261599 + ], + [ + 8.4455502, + 49.0261599 + ], + [ + 8.4455502, + 49.0258263 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "mfbehrens99", + "uid": "9645335", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #climbing", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T21:20:35Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 2.08967040002884e-7, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "climbing", + "imagery": "osm", + "language": "en", + "theme-creator": "Christian Neumann " + } + } + }, + { + "id": 112625573, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 5.5603149, + 49.7179279 + ], + [ + 5.5603149, + 49.7179279 + ], + [ + 5.5603149, + 49.7179279 + ], + [ + 5.5603149, + 49.7179279 + ], + [ + 5.5603149, + 49.7179279 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "juminet", + "uid": "812669", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T19:57:25Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "artwork", + "imagery": "osm", + "language": "fr", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112623519, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.0297728, + 49.7231956 + ], + [ + 8.0312295, + 49.7231956 + ], + [ + 8.0312295, + 49.7255962 + ], + [ + 8.0297728, + 49.7255962 + ], + [ + 8.0297728, + 49.7231956 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Tanzbärli", + "uid": "11052582", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #sport_pitches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T18:43:02Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.00000349695402000281, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "sport_pitches", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112622176, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2097007, + 41.536822 + ], + [ + 2.2097007, + 41.536822 + ], + [ + 2.2097007, + 41.536822 + ], + [ + 2.2097007, + 41.536822 + ], + [ + 2.2097007, + 41.536822 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Moisès", + "uid": "12884230", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #trees", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T17:58:59Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "trees", + "imagery": "osm", + "language": "en", + "theme-creator": "Midgard" + } + } + }, + { + "id": 112621911, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2860721, + 51.3408964 + ], + [ + 3.2860721, + 51.3408964 + ], + [ + 3.2860721, + 51.3408964 + ], + [ + 3.2860721, + 51.3408964 + ], + [ + 3.2860721, + 51.3408964 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #artwork", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T17:50:39Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "artwork", + "imagery": "osm", + "language": "nl", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112621112, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 4.4012481, + 50.8360901 + ], + [ + 4.4066974, + 50.8360901 + ], + [ + 4.4066974, + 50.8385235 + ], + [ + 4.4012481, + 50.8385235 + ], + [ + 4.4012481, + 50.8360901 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Thierry1030", + "uid": "286563", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclestreets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T17:26:05Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 0.0000132603266200045, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclestreets", + "imagery": "osm", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112619905, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.6153278, + 51.9250815 + ], + [ + 7.6153278, + 51.9250815 + ], + [ + 7.6153278, + 51.9250815 + ], + [ + 7.6153278, + 51.9250815 + ], + [ + 7.6153278, + 51.9250815 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "hke2912", + "uid": "5154951", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T16:51:07Z", + "reviewed_features": [], + "create": 1, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "cyclofix", + "answer": 4, + "create": 1, + "imagery": "CartoDB.Voyager", + "language": "de" + } + } + }, + { + "id": 112619236, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.6187158, + 51.9263368 + ], + [ + 7.6196777, + 51.9263368 + ], + [ + 7.6196777, + 51.9280086 + ], + [ + 7.6187158, + 51.9280086 + ], + [ + 7.6187158, + 51.9263368 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "hke2912", + "uid": "5154951", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #benches", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T16:30:09Z", + "reviewed_features": [], + "create": 3, + "modify": 5, + "delete": 0, + "area": 0.00000160810441999713, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "benches", + "answer": 12, + "create": 3, + "imagery": "osm", + "language": "de" + } + } + }, + { + "id": 112618493, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.4213985, + 47.3818429 + ], + [ + 8.4237779, + 47.3818429 + ], + [ + 8.4237779, + 47.3843273 + ], + [ + 8.4213985, + 47.3843273 + ], + [ + 8.4213985, + 47.3818429 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "skl1", + "uid": "311771", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T16:08:54Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0.00000591138135999744, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112618387, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.6206011, + 51.9316626 + ], + [ + 7.6207861, + 51.9316626 + ], + [ + 7.6207861, + 51.9318794 + ], + [ + 7.6206011, + 51.9318794 + ], + [ + 7.6206011, + 51.9316626 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "hke2912", + "uid": "5154951", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #benches", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T16:04:37Z", + "reviewed_features": [], + "create": 3, + "modify": 4, + "delete": 0, + "area": 4.01079999994501e-8, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "benches", + "answer": 11, + "create": 3, + "imagery": "osm", + "language": "de" + } + } + }, + { + "id": 112613205, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 14.9882162, + 51.1523834 + ], + [ + 14.9882162, + 51.1523834 + ], + [ + 14.9882162, + 51.1523834 + ], + [ + 14.9882162, + 51.1523834 + ], + [ + 14.9882162, + 51.1523834 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "staggerlee", + "uid": "38420", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #toilets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T13:57:50Z", + "reviewed_features": [], + "create": 1, + "modify": 3, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "toilets", + "imagery": "osm", + "language": "de", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112609243, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.5655974, + 47.548397 + ], + [ + 7.5916798, + 47.548397 + ], + [ + 7.5916798, + 47.5600495 + ], + [ + 7.5655974, + 47.5600495 + ], + [ + 7.5655974, + 47.548397 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "KaiPankrath", + "uid": "4067009", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #drinking_water", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T12:09:09Z", + "reviewed_features": [], + "create": 0, + "modify": 43, + "delete": 0, + "area": 0.000303925165999914, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "drinking_water", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112609034, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.7507024, + 49.4242671 + ], + [ + 7.751568, + 49.4242671 + ], + [ + 7.751568, + 49.4261699 + ], + [ + 7.7507024, + 49.4261699 + ], + [ + 7.7507024, + 49.4242671 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Magnus1234", + "uid": "14305060", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cyclofix", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T12:02:29Z", + "reviewed_features": [], + "create": 1, + "modify": 5, + "delete": 0, + "area": 0.00000164706367999686, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cyclofix", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112608864, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7.7597241, + 49.4281582 + ], + [ + 7.7597241, + 49.4281582 + ], + [ + 7.7597241, + 49.4281582 + ], + [ + 7.7597241, + 49.4281582 + ], + [ + 7.7597241, + 49.4281582 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [ + { + "id": 40, + "name": "New mapper" + } + ], + "tags": [], + "features": [], + "user": "Magnus1234", + "uid": "14305060", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #cycle_infra", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-17T11:57:35Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": true, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "cycle_infra", + "imagery": "CartoDB.Voyager", + "language": "en", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112607574, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.234271, + -39.8222353 + ], + [ + -73.2342086, + -39.8222353 + ], + [ + -73.2342086, + -39.8221857 + ], + [ + -73.234271, + -39.8221857 + ], + [ + -73.234271, + -39.8222353 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Awo", + "uid": "196556", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #climbing", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T11:23:38Z", + "reviewed_features": [], + "create": 0, + "modify": 2, + "delete": 0, + "area": 3.09504000047643e-9, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "climbing", + "imagery": "Mapbox", + "language": "en", + "theme-creator": "Christian Neumann " + } + } + }, + { + "id": 112605904, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.2327807, + 48.8234134 + ], + [ + 2.3892111, + 48.8234134 + ], + [ + 2.3892111, + 48.8419175 + ], + [ + 2.2327807, + 48.8419175 + ], + [ + 2.2327807, + 48.8234134 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Sukkoria", + "uid": "3083013", + "editor": "MapComplete 0.2.2a", + "comment": "Adding data with #MapComplete for theme #climbing", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T10:36:20Z", + "reviewed_features": [], + "create": 0, + "modify": 24, + "delete": 0, + "area": 0.00289460376464019, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "theme": "climbing", + "language": "en", + "theme-creator": "Christian Neumann " + } + } + }, + { + "id": 112604326, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 18.5302743, + 54.5212696 + ], + [ + 18.5302743, + 54.5212696 + ], + [ + 18.5302743, + 54.5212696 + ], + [ + 18.5302743, + 54.5212696 + ], + [ + 18.5302743, + 54.5212696 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "RicoElectrico", + "uid": "604437", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #aed", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T09:44:59Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "aed", + "imagery": "osm", + "language": "pl", + "theme-creator": "MapComplete" + } + } + }, + { + "id": 112597311, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 174.7825092, + -36.85054 + ], + [ + 174.7825092, + -36.85054 + ], + [ + 174.7825092, + -36.85054 + ], + [ + 174.7825092, + -36.85054 + ], + [ + 174.7825092, + -36.85054 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "ralley", + "uid": "670820", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #vets", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-17T03:31:31Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "vets", + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112594776, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.2186897, + 51.2131816 + ], + [ + 3.2186897, + 51.2131816 + ], + [ + 3.2186897, + 51.2131816 + ], + [ + 3.2186897, + 51.2131816 + ], + [ + 3.2186897, + 51.2131816 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "Pieter Vander Vennet", + "uid": "3818858", + "editor": "MapComplete 0.11.0-alpha-4", + "comment": "Adding data with #MapComplete for theme #cafes_and_pubs", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-16T22:28:14Z", + "reviewed_features": [], + "create": 0, + "modify": 4, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "pietervdvn.github.io", + "path": "mc/develop/", + "theme": "cafes_and_pubs", + "answer": 6, + "imagery": "osm", + "language": "en" + } + } + }, + { + "id": 112589924, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.4250032, + 51.1032661 + ], + [ + 3.4250032, + 51.1032661 + ], + [ + 3.4250032, + 51.1032661 + ], + [ + 3.4250032, + 51.1032661 + ], + [ + 3.4250032, + 51.1032661 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/cyclenodenetworks/cyclenodenetworks.json", + "comments_count": 0, + "source": "survey", + "imagery_used": "Not reported", + "date": "2021-10-16T18:51:40Z", + "reviewed_features": [], + "create": 0, + "modify": 1, + "delete": 0, + "area": 0, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/cyclenodenetworks/cyclenodenetworks.json", + "imagery": "osm", + "language": "en", + "theme-creator": "L'imaginaire" + } + } + }, + { + "id": 112588973, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.3940572, + 51.0837174 + ], + [ + 3.3993392, + 51.0837174 + ], + [ + 3.3993392, + 51.1047399 + ], + [ + 3.3940572, + 51.1047399 + ], + [ + 3.3940572, + 51.0837174 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #benches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-16T18:10:27Z", + "reviewed_features": [], + "create": 0, + "modify": 3, + "delete": 0, + "area": 0.000111040845, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "benches", + "imagery": "osm", + "language": "en", + "theme-creator": "Florian Edelmann" + } + } + }, + { + "id": 112588967, + "type": "Feature", + "geometry": null, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #benches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-16T18:10:17Z", + "reviewed_features": [], + "create": 0, + "modify": 0, + "delete": 0, + "area": null, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "benches", + "imagery": "osm", + "language": "en", + "theme-creator": "Florian Edelmann" + } + } + }, + { + "id": 112588960, + "type": "Feature", + "geometry": null, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #benches", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-16T18:10:06Z", + "reviewed_features": [], + "create": 0, + "modify": 0, + "delete": 0, + "area": null, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "benches", + "imagery": "osm", + "language": "en", + "theme-creator": "Florian Edelmann" + } + } + }, + { + "id": 112588071, + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 3.3313568, + 51.0988663 + ], + [ + 3.427917, + 51.0988663 + ], + [ + 3.427917, + 51.1116026 + ], + [ + 3.3313568, + 51.1116026 + ], + [ + 3.3313568, + 51.0988663 + ] + ] + ] + }, + "properties": { + "check_user": null, + "reasons": [], + "tags": [], + "features": [], + "user": "L'imaginaire", + "uid": "654234", + "editor": "MapComplete 0.10.6", + "comment": "Adding data with #MapComplete for theme #https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/cyclenodenetworks/cyclenodenetworks.json", + "comments_count": 0, + "source": "Not reported", + "imagery_used": "Not reported", + "date": "2021-10-16T17:37:19Z", + "reviewed_features": [], + "create": 0, + "modify": 5, + "delete": 0, + "area": 0.00122981967526002, + "is_suspect": false, + "harmful": null, + "checked": false, + "check_date": null, + "metadata": { + "host": "mapcomplete.osm.be", + "theme": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/cyclenodenetworks/cyclenodenetworks.json", + "imagery": "osm", + "language": "en", + "theme-creator": "L'imaginaire" + } + } + }, { "id": 112583752, "type": "Feature", diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index bd363df9b..a27eadc97 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -20,42 +20,6 @@ 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. - download-control-toggle -------------------------- - - Whether or not the download panel is shown The default value is _false_ - - - filter-toggle ---------------- - - Whether or not the filter view is shown The default value is _false_ - - - tab ------ - - The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ - - - z ---- - - The initial/current zoom level The default value is _0_ - - - lat ------ - - The initial/current latitude The default value is _0_ - - - lon ------ - - The initial/current longitude of the app The default value is _0_ - - fs-userbadge -------------- @@ -92,10 +56,10 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. Disables/enables the help menu or welcome message The default value is _true_ - fs-iframe ------------ + fs-iframe-popout +------------------ - Disables/Enables the iframe-popup The default value is _false_ + 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 @@ -134,6 +98,12 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. Enable the PDF download button The default value is _false_ + backend +--------- + + The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ + + test ------ @@ -152,12 +122,6 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_ - backend ---------- - - The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ - - overpassUrl ------------- @@ -170,10 +134,16 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. Set a different timeout (in seconds) for queries in overpass The default value is _30_ - custom-css ------------- + overpassMaxZoom +----------------- - If specified, the custom css from the given link will be loaded additionaly The default value is __ + point to switch between OSM-api and overpass The default value is _17_ + + + osmApiTileSize +---------------- + + Tilesize when the OSM-API is used to fetch data within a BBOX The default value is _18_ background diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 86e656b50..6e3c7bd75 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -1,13 +1,16 @@ -import * as L from "leaflet"; import {UIEventSource} from "../UIEventSource"; import Svg from "../../Svg"; -import Img from "../../UI/Base/Img"; import {LocalStorageSource} from "../Web/LocalStorageSource"; import {VariableUiElement} from "../../UI/Base/VariableUIElement"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {QueryParameters} from "../Web/QueryParameters"; +import FeatureSource from "../FeatureSource/FeatureSource"; +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; export default class GeoLocationHandler extends VariableUiElement { + + public readonly currentLocation : FeatureSource + /** * Wether or not the geolocation is active, aka the user requested the current location * @private @@ -25,20 +28,12 @@ export default class GeoLocationHandler extends VariableUiElement { * @private */ private readonly _permission: UIEventSource; - /*** - * The marker on the map, in order to update it - * @private - */ - private _marker: L.Marker; /** * Literally: _currentGPSLocation.data != undefined * @private */ private readonly _hasLocation: UIEventSource; - private readonly _currentGPSLocation: UIEventSource<{ - latlng: any; - accuracy: number; - }>; + private readonly _currentGPSLocation: UIEventSource; /** * Kept in order to update the marker * @private @@ -63,8 +58,8 @@ export default class GeoLocationHandler extends VariableUiElement { private readonly _layoutToUse: LayoutConfig; constructor( - currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, - leafletMap: UIEventSource, + currentGPSLocation: UIEventSource, + leafletMap: UIEventSource, layoutToUse: LayoutConfig ) { const hasLocation = currentGPSLocation.map( @@ -182,10 +177,25 @@ export default class GeoLocationHandler extends VariableUiElement { } }) - + this.currentLocation = new StaticFeatureSource([], false) this._currentGPSLocation.addCallback((location) => { self._previousLocationGrant.setData("granted"); + const feature = { + "type": "Feature", + properties: { + "user:location":"yes", + "accuracy":location.accuracy, + "speed":location.speed, + }, + geometry:{ + type:"Point", + coordinates: [location.longitude, location.latitude], + } + } + + self.currentLocation.features.setData([{feature, freshness: new Date()}]) + const timeSinceRequest = (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; if (timeSinceRequest < 30) { @@ -194,33 +204,8 @@ export default class GeoLocationHandler extends VariableUiElement { self.MoveToCurrentLoction(); } - let color = "#1111cc"; - try { - color = getComputedStyle(document.body).getPropertyValue( - "--catch-detail-color" - ); - } catch (e) { - console.error(e); - } - const icon = L.icon({ - iconUrl: Img.AsData(Svg.location.replace(/#000000/g, color).replace(/#000/g, color)), - iconSize: [40, 40], // size of the icon - iconAnchor: [20, 20], // point of the icon which will correspond to marker's location - }); - - const map = self._leafletMap.data; - if(map === undefined){ - return; - } - - const newMarker = L.marker(location.latlng, {icon: icon}); - newMarker.addTo(map); - - if (self._marker !== undefined) { - map.removeLayer(self._marker); - } - self._marker = newMarker; }); + } private init(askPermission: boolean, forceZoom: boolean) { @@ -261,8 +246,8 @@ export default class GeoLocationHandler extends VariableUiElement { this._lastUserRequest = undefined; if ( - this._currentGPSLocation.data.latlng[0] === 0 && - this._currentGPSLocation.data.latlng[1] === 0 + this._currentGPSLocation.data.latitude === 0 && + this._currentGPSLocation.data.longitude === 0 ) { console.debug("Not moving to GPS-location: it is null island"); return; @@ -275,20 +260,22 @@ export default class GeoLocationHandler extends VariableUiElement { if (b !== true) { // B is an array with our locklocation inRange = - b[0][0] <= location.latlng[0] && - location.latlng[0] <= b[1][0] && - b[0][1] <= location.latlng[1] && - location.latlng[1] <= b[1][1]; + b[0][0] <= location.latitude && + location.latitude <= b[1][0] && + b[0][1] <= location.longitude && + location.longitude <= b[1][1]; } } if (!inRange) { console.log( "Not zooming to GPS location: out of bounds", b, - location.latlng + location ); } else { - this._leafletMap.data.setView(location.latlng, targetZoom); + const currentZoom = this._leafletMap.data.getZoom() + + this._leafletMap.data.setView([location.latitude, location.longitude], Math.max(targetZoom ?? 0, currentZoom)); } } @@ -312,10 +299,7 @@ export default class GeoLocationHandler extends VariableUiElement { navigator.geolocation.watchPosition( function (position) { - self._currentGPSLocation.setData({ - latlng: [position.coords.latitude, position.coords.longitude], - accuracy: position.coords.accuracy, - }); + self._currentGPSLocation.setData(position.coords); }, function () { console.warn("Could not get location with navigator.geolocation"); diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 765c9ee19..2aa2c942e 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -6,6 +6,8 @@ import {ElementStorage} from "../ElementStorage"; import {Changes} from "../Osm/Changes"; import {OsmObject} from "../Osm/OsmObject"; import {OsmConnection} from "../Osm/OsmConnection"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import SimpleMetaTagger from "../SimpleMetaTagger"; export default class SelectedElementTagsUpdater { @@ -14,13 +16,14 @@ export default class SelectedElementTagsUpdater { "changeset", "user", "uid", - "id"] ) + "id"]) constructor(state: { selectedElement: UIEventSource, allElements: ElementStorage, changes: Changes, - osmConnection: OsmConnection + osmConnection: OsmConnection, + layoutToUse: LayoutConfig }) { @@ -37,7 +40,8 @@ export default class SelectedElementTagsUpdater { selectedElement: UIEventSource, allElements: ElementStorage, changes: Changes, - osmConnection: OsmConnection + osmConnection: OsmConnection, + layoutToUse: LayoutConfig }) { @@ -70,11 +74,18 @@ export default class SelectedElementTagsUpdater { selectedElement: UIEventSource, allElements: ElementStorage, changes: Changes, - osmConnection: OsmConnection + osmConnection: OsmConnection, + layoutToUse: LayoutConfig }, latestTags: any, id: string ) { try { + const leftRightSensitive = state.layoutToUse.isLeftRightSensitive() + + if (leftRightSensitive) { + SimpleMetaTagger.removeBothTagging(latestTags) + } + const pendingChanges = state.changes.pendingChanges.data .filter(change => change.type + "/" + change.id === id) .filter(change => change.tags !== undefined); @@ -92,6 +103,7 @@ export default class SelectedElementTagsUpdater { } } + // With the changes applied, we merge them onto the upstream object let somethingChanged = false; const currentTagsSource = state.allElements.getEventSourceById(id); @@ -115,7 +127,7 @@ export default class SelectedElementTagsUpdater { if (currentKey.startsWith("_")) { continue } - if(this.metatags.has(currentKey)){ + if (this.metatags.has(currentKey)) { continue } if (currentKey in latestTags) { diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 7d431c7af..aba97ecb1 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -10,7 +10,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; * Makes sure the hash shows the selected element and vice-versa. */ export default class SelectedFeatureHandler { - private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filter","", undefined]) + private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filters","", undefined]) private readonly hash: UIEventSource; private readonly state: { selectedElement: UIEventSource, @@ -114,6 +114,7 @@ export default class SelectedFeatureHandler { // Hash has been cleared - we clear the selected element state.selectedElement.setData(undefined); } else { + // we search the element to select const feature = state.allElements.ContainingFeatures.get(h) if (feature === undefined) { diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 0205b1533..ccd320125 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -1,5 +1,6 @@ import * as turf from "@turf/turf"; import {TileRange, Tiles} from "../Models/TileRange"; +import {GeoOperations} from "./GeoOperations"; export class BBox { @@ -22,7 +23,7 @@ export class BBox { this.minLon = Math.min(this.minLon, coordinate[0]); this.minLat = Math.min(this.minLat, coordinate[1]); } - + this.maxLon = Math.min(this.maxLon, 180) this.maxLat = Math.min(this.maxLat, 90) this.minLon = Math.max(this.minLon, -180) @@ -115,14 +116,19 @@ export class BBox { getSouth() { return this.minLat } + + contains(lonLat: [number, number]){ + return this.minLat <= lonLat[1] && lonLat[1] <= this.maxLat + && this.minLon<= lonLat[0] && lonLat[0] <= this.maxLon + } pad(factor: number, maxIncrease = 2): BBox { - + const latDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLat - this.minLat) * factor) - const lonDiff =Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor) + const lonDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor) return new BBox([[ this.minLon - lonDiff, - this.minLat - latDiff + this.minLat - latDiff ], [this.maxLon + lonDiff, this.maxLat + latDiff]]) } @@ -161,4 +167,16 @@ export class BBox { const boundslr = Tiles.tile_bounds_lon_lat(lr.z, lr.x, lr.y) return new BBox([].concat(boundsul, boundslr)) } + + toMercator(): { minLat: number, maxLat: number, minLon: number, maxLon: number } { + const [minLon, minLat] = GeoOperations.ConvertWgs84To900913([this.minLon, this.minLat]) + const [maxLon, maxLat] = GeoOperations.ConvertWgs84To900913([this.maxLon, this.maxLat]) + + return { + minLon, maxLon, + minLat, maxLat + } + + + } } \ No newline at end of file diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts index 9af285952..b29e5f8b5 100644 --- a/Logic/DetermineLayout.ts +++ b/Logic/DetermineLayout.ts @@ -10,6 +10,7 @@ import {UIEventSource} from "./UIEventSource"; import {LocalStorageSource} from "./Web/LocalStorageSource"; import LZString from "lz-string"; import * as personal from "../assets/themes/personal/personal.json"; +import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; export default class DetermineLayout { @@ -18,7 +19,6 @@ export default class DetermineLayout { */ public static async GetLayout(): Promise<[LayoutConfig, string]> { - const loadCustomThemeParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme") const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data); @@ -73,17 +73,14 @@ export default class DetermineLayout { try { - const data = await Utils.downloadJson(link) + const parsed = await Utils.downloadJson(link) + console.log("Got ", parsed) + LegacyJsonConvert.fixThemeConfig(parsed) try { - let parsed = data; - if (typeof parsed == "string") { - parsed = JSON.parse(parsed); - } - // Overwrite the id to the url parsed.id = link; - return new LayoutConfig(parsed, false).patchImages(link, data); + return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed)); } catch (e) { - + console.error(e) DetermineLayout.ShowErrorOnCustomTheme( `${link} is invalid:`, new FixedUiElement(e) @@ -92,6 +89,7 @@ export default class DetermineLayout { } } catch (e) { + console.error(e) DetermineLayout.ShowErrorOnCustomTheme( `${link} is invalid - probably not found or invalid JSON:`, new FixedUiElement(e) @@ -107,7 +105,7 @@ export default class DetermineLayout { try { // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter const dedicatedHashFromLocalStorage = LocalStorageSource.Get( - "user-layout-" + userLayoutParam.data.replace(" ", "_") + "user-layout-" + userLayoutParam.data?.replace(" ", "_") ); if (dedicatedHashFromLocalStorage.data?.length < 10) { dedicatedHashFromLocalStorage.setData(undefined); @@ -134,15 +132,18 @@ export default class DetermineLayout { try { json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash))) } catch (e) { + console.error(e) DetermineLayout.ShowErrorOnCustomTheme("Could not decode the hash", new FixedUiElement("Not a valid (LZ-compressed) JSON")) return null; } } + LegacyJsonConvert.fixThemeConfig(json) const layoutToUse = new LayoutConfig(json, false); userLayoutParam.setData(layoutToUse.id); return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; } catch (e) { + console.error(e) if (hash === undefined || hash.length < 10) { DetermineLayout.ShowErrorOnCustomTheme("Could not load a theme from the hash", new FixedUiElement("Hash does not contain data")) } diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index 8f0265bb1..df7e3a585 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -57,13 +57,14 @@ export class ExtraFunction { doc: "Gives a list of features from the specified layer which this feature (partly) overlaps with. " + "If the current feature is a point, all features that embed the point are given. " + "The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point.\n" + + "The resulting list is sorted in descending order by overlap. The feature with the most overlap will thus be the first in the list" + "\n" + "For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')`", args: ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"] }, (params, feat) => { return (...layerIds: string[]) => { - const result = [] + const result : {feat:any, overlap: number}[]= [] const bbox = BBox.get(feat) @@ -79,6 +80,9 @@ export class ExtraFunction { result.push(...GeoOperations.calculateOverlap(feat, otherLayer)); } } + + result.sort((a, b) => b.overlap - a.overlap) + return result; } } @@ -163,12 +167,41 @@ export class ExtraFunction { } ) + private static readonly GetParsed = new ExtraFunction( + { + name: "get", + doc: "Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ...", + args: ["key"] + }, + (params, feat) => { + return key => { + const value = feat.properties[key] + if (value === undefined) { + return undefined; + } + try { + const parsed = JSON.parse(value) + if(parsed === null){ + return undefined; + } + return parsed; + } catch (e) { + console.warn("Could not parse property " + key + " due to: " + e + ", the value is " + value) + return undefined; + } + + } + + } + ) + private static readonly allFuncs: ExtraFunction[] = [ ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc, ExtraFunction.ClosestObjectFunc, ExtraFunction.ClosestNObjectFunc, - ExtraFunction.Memberships + ExtraFunction.Memberships, + ExtraFunction.GetParsed ]; private readonly _name: string; private readonly _args: string[]; @@ -222,7 +255,6 @@ export class ExtraFunction { const maxFeatures = options?.maxFeatures ?? 1 const maxDistance = options?.maxDistance ?? 500 const uniqueTag: string | undefined = options?.uniqueTag - console.log("Requested closestN") if (typeof features === "string") { const name = features const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)) @@ -238,7 +270,7 @@ export class ExtraFunction { let closestFeatures: { feat: any, distance: number }[] = []; for (const featureList of features) { for (const otherFeature of featureList) { - if (otherFeature === feature || otherFeature.id === feature.id) { + if (otherFeature === feature || otherFeature.properties.id === feature.properties.id) { continue; // We ignore self } const distance = GeoOperations.distanceBetween( @@ -249,6 +281,11 @@ export class ExtraFunction { console.error("Could not calculate the distance between", feature, "and", otherFeature) throw "Undefined distance!" } + + if (distance === 0) { + console.trace("Got a suspiciously zero distance between", otherFeature, "and self-feature", feature) + } + if (distance > maxDistance) { continue } diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index b5ea30bd3..4e90f1616 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -12,7 +12,6 @@ import OverpassFeatureSource from "../Actors/OverpassFeatureSource"; import {Changes} from "../Osm/Changes"; import GeoJsonSource from "./Sources/GeoJsonSource"; import Loc from "../../Models/Loc"; -import WayHandlingApplyingFeatureSource from "./Sources/WayHandlingApplyingFeatureSource"; import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor"; import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource"; import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor"; @@ -26,6 +25,8 @@ import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource"; import {OsmConnection} from "../Osm/OsmConnection"; import {Tiles} from "../../Models/TileRange"; import TileFreshnessCalculator from "./TileFreshnessCalculator"; +import {ElementStorage} from "../ElementStorage"; +import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource"; /** @@ -85,7 +86,8 @@ export default class FeaturePipeline { readonly overpassMaxZoom: UIEventSource; readonly osmConnection: OsmConnection readonly currentBounds: UIEventSource, - readonly osmApiTileSize: UIEventSource + readonly osmApiTileSize: UIEventSource, + readonly allElements: ElementStorage }) { this.state = state; @@ -98,7 +100,7 @@ export default class FeaturePipeline { this.osmSourceZoomLevel = state.osmApiTileSize.data; const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12)) this.relationTracker = new RelationsTracker() - + state.changes.allChanges.addCallbackAndRun(allChanges => { allChanges.filter(ch => ch.id < 0 && ch.changes !== undefined) .map(ch => ch.changes) @@ -127,9 +129,7 @@ export default class FeaturePipeline { // This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile const srcFiltered = new FilteringFeatureSource(state, src.tileIndex, - new WayHandlingApplyingFeatureSource( new ChangeGeometryApplicator(src, state.changes) - ) ) handleFeatureSource(srcFiltered) @@ -147,6 +147,11 @@ export default class FeaturePipeline { this.freshnesses.set(id, new TileFreshnessCalculator()) + if(id === "type_node"){ + // Handles by the 'FullNodeDatabaseSource' + continue; + } + if (source.geojsonSource === undefined) { // This is an OSM layer // We load the cached values and register them @@ -205,7 +210,9 @@ export default class FeaturePipeline { neededTiles: neededTilesFromOsm, handleTile: tile => { new RegisteringAllFromFeatureSourceActor(tile) - new SaveTileToLocalStorageActor(tile, tile.tileIndex) + if (tile.layer.layerDef.maxAgeOfCache > 0) { + new SaveTileToLocalStorageActor(tile, tile.tileIndex) + } perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile) tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile)) @@ -213,10 +220,24 @@ export default class FeaturePipeline { state: state, markTileVisited: (tileId) => state.filteredLayers.data.forEach(flayer => { - SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date()) + if (flayer.layerDef.maxAgeOfCache > 0) { + SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date()) + } self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date()) }) }) + + if(state.layoutToUse.trackAllNodes){ + const fullNodeDb = new FullNodeDatabaseSource( + state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0], + tile => { + new RegisteringAllFromFeatureSourceActor(tile) + perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile) + tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile)) + }) + + osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => fullNodeDb.handleOsmJson(osmJson, tileId)) + } const updater = this.initOverpassUpdater(state, useOsmApi) @@ -262,7 +283,7 @@ export default class FeaturePipeline { // Whenever fresh data comes in, we need to update the metatagging - self.newDataLoadedSignal.stabilized(1000).addCallback(_ => { + self.newDataLoadedSignal.stabilized(250).addCallback(src => { self.updateAllMetaTagging() }) @@ -387,7 +408,7 @@ export default class FeaturePipeline { window.setTimeout( () => { const layerDef = src.layer.layerDef; - MetaTagging.addMetatags( + const somethingChanged = MetaTagging.addMetatags( src.features.data, { memberships: this.relationTracker, @@ -408,9 +429,10 @@ export default class FeaturePipeline { private updateAllMetaTagging() { const self = this; + console.debug("Updating the meta tagging of all tiles as new data got loaded") this.perLayerHierarchy.forEach(hierarchy => { - hierarchy.loadedTiles.forEach(src => { - self.applyMetaTags(src) + hierarchy.loadedTiles.forEach(tile => { + self.applyMetaTags(tile) }) }) diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 0c2c9d92a..cf0475d26 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,9 +1,10 @@ import {UIEventSource} from "../../UIEventSource"; -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; import Hash from "../../Web/Hash"; import {BBox} from "../../BBox"; +import {ElementStorage} from "../../ElementStorage"; +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { public features: UIEventSource<{ feature: any; freshness: Date }[]> = @@ -12,79 +13,107 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti public readonly layer: FilteredLayer; public readonly tileIndex: number public readonly bbox: BBox + private readonly upstream: FeatureSourceForLayer; + private readonly state: { + locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource, + allElements: ElementStorage + }; constructor( state: { locationControl: UIEventSource<{ zoom: number }>, selectedElement: UIEventSource, + allElements: ElementStorage }, tileIndex, upstream: FeatureSourceForLayer ) { - const self = this; this.name = "FilteringFeatureSource(" + upstream.name + ")" this.tileIndex = tileIndex this.bbox = BBox.fromTileIndex(tileIndex) + this.upstream = upstream + this.state = state this.layer = upstream.layer; const layer = upstream.layer; - - function update() { - - const features: { feature: any; freshness: Date }[] = upstream.features.data; - const newFeatures = features.filter((f) => { - if ( - state.selectedElement.data?.id === f.feature.id || - f.feature.id === Hash.hash.data) { - // This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away - return true; - } - - const isShown = layer.layerDef.isShown; - const tags = f.feature.properties; - if (isShown.IsKnown(tags)) { - const result = layer.layerDef.isShown.GetRenderValue( - f.feature.properties - ).txt; - if (result !== "yes") { - return false; - } - } - - const tagsFilter = layer.appliedFilters.data; - for (const filter of tagsFilter ?? []) { - const neededTags = filter.filter.options[filter.selected].osmTags - if (!neededTags.matchesProperties(f.feature.properties)) { - // Hidden by the filter on the layer itself - we want to hide it no matter wat - return false; - } - } - - - return true; - }); - - self.features.setData(newFeatures); - } - + const self = this; upstream.features.addCallback(() => { - update(); + self.update(); }); layer.appliedFilters.addCallback(_ => { - update() + self.update() }) - update(); + this._is_dirty.stabilized(250).addCallbackAndRunD(dirty => { + if (dirty) { + self.update() + } + }) + + this.update(); } - private static showLayer( - layer: { - isDisplayed: UIEventSource; - layerDef: LayerConfig; - }) { - return layer.isDisplayed.data; + private readonly _alreadyRegistered = new Set>(); + private readonly _is_dirty = new UIEventSource(false) + private registerCallback(feature: any, layer: LayerConfig) { + const src = this.state.allElements.addOrGetElement(feature) + if (this._alreadyRegistered.has(src)) { + return + } + this._alreadyRegistered.add(src) + if (layer.isShown !== undefined) { + + const self = this; + src.map(tags => layer.isShown?.GetRenderValue(tags, "yes").txt).addCallbackAndRunD(isShown => { + self._is_dirty.setData(true) + }) + } } + + public update() { + const self = this; + const layer = this.upstream.layer; + const features: { feature: any; freshness: Date }[] = this.upstream.features.data; + const newFeatures = features.filter((f) => { + + self.registerCallback(f.feature, layer.layerDef) + + if ( + this.state.selectedElement.data?.id === f.feature.id || + f.feature.id === Hash.hash.data) { + // This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away + return true; + } + + const isShown = layer.layerDef.isShown; + const tags = f.feature.properties; + if (isShown.IsKnown(tags)) { + const result = layer.layerDef.isShown.GetRenderValue( + f.feature.properties + ).txt; + if (result !== "yes") { + return false; + } + } + + const tagsFilter = layer.appliedFilters.data; + for (const filter of tagsFilter ?? []) { + const neededTags = filter.filter.options[filter.selected].osmTags + if (!neededTags.matchesProperties(f.feature.properties)) { + // Hidden by the filter on the layer itself - we want to hide it no matter wat + return false; + } + } + + + return true; + }); + + this.features.setData(newFeatures); + this._is_dirty.setData(false) + } + } diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index be93dedc7..68f8fab82 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -7,6 +7,7 @@ import {Utils} from "../../../Utils"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; import {Tiles} from "../../../Models/TileRange"; import {BBox} from "../../BBox"; +import {GeoOperations} from "../../GeoOperations"; export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { @@ -14,7 +15,6 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; public readonly name; public readonly isOsmCache: boolean - private onFail: ((errorMsg: any, url: string) => void) = undefined; private readonly seenids: Set = new Set() public readonly layer: FilteredLayer; @@ -44,10 +44,20 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id); if (zxy !== undefined) { const [z, x, y] = zxy; + let tile_bbox = BBox.fromTile(z, x, y) + let bounds : { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox + if(this.layer.layerDef.source.mercatorCrs){ + bounds = tile_bbox.toMercator() + } url = url .replace('{z}', "" + z) .replace('{x}', "" + x) .replace('{y}', "" + y) + .replace('{y_min}',""+bounds.minLat) + .replace('{y_max}',""+bounds.maxLat) + .replace('{x_min}',""+bounds.minLon) + .replace('{x_max}',""+bounds.maxLon) + this.tileIndex = Tiles.tile_index(z, x, y) this.bbox = BBox.fromTile(z, x, y) } else { @@ -71,6 +81,10 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { if(json.features === undefined || json.features === null){ return; } + + if(self.layer.layerDef.source.mercatorCrs){ + json = GeoOperations.GeoJsonToWGS84(json) + } const time = new Date(); const newFeatures: { feature: any, freshness: Date } [] = [] diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index 0a1c67f41..bd73f04c2 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -31,7 +31,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { // Already handled !seenChanges.has(ch))) .addCallbackAndRunD(changes => { - if (changes.length === 0) { return; } @@ -71,7 +70,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { const w = new OsmWay(change.id) w.tags = tags w.nodes = change.changes["nodes"] - w.coordinates = change.changes["coordinates"].map(coor => coor.reverse()) + w.coordinates = change.changes["coordinates"].map(coor => [coor[1], coor[0]]) add(w.asGeoJson()) break; case "relation": diff --git a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts new file mode 100644 index 000000000..eb0d4b10d --- /dev/null +++ b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts @@ -0,0 +1,105 @@ +/** + * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indiciates with what renderConfig it should be rendered. + */ +import {UIEventSource} from "../../UIEventSource"; +import {GeoOperations} from "../../GeoOperations"; +import FeatureSource from "../FeatureSource"; +import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"; +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; + + +export default class RenderingMultiPlexerFeatureSource { + public readonly features: UIEventSource<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>; + + constructor(upstream: FeatureSource, layer: LayerConfig) { + this.features = upstream.features.map( + features => { + if (features === undefined) { + return; + } + + const pointRenderObjects: { rendering: PointRenderingConfig, index: number }[] = layer.mapRendering.map((r, i) => ({ + rendering: r, + index: i + })) + const pointRenderings = pointRenderObjects.filter(r => r.rendering.location.has("point")) + const centroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("centroid")) + const startRenderings = pointRenderObjects.filter(r => r.rendering.location.has("start")) + const endRenderings = pointRenderObjects.filter(r => r.rendering.location.has("end")) + + const lineRenderObjects = layer.lineRendering + + const withIndex: (any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[] = []; + + + function addAsPoint(feat, rendering, coordinate) { + const patched = { + ...feat, + pointRenderingIndex: rendering.index + } + patched.geometry = { + type: "Point", + coordinates: coordinate + } + withIndex.push(patched) + } + + for (const f of features) { + const feat = f.feature; + if (feat.geometry.type === "Point") { + + for (const rendering of pointRenderings) { + withIndex.push({ + ...feat, + pointRenderingIndex: rendering.index + }) + } + } else { + // This is a a line + for (const rendering of centroidRenderings) { + addAsPoint(feat, rendering, GeoOperations.centerpointCoordinates(feat)) + } + + if (feat.geometry.type === "LineString") { + const coordinates = feat.geometry.coordinates + for (const rendering of startRenderings) { + addAsPoint(feat, rendering, coordinates[0]) + } + for (const rendering of endRenderings) { + const coordinate = coordinates[coordinates.length - 1] + addAsPoint(feat, rendering, coordinate) + } + } + + if (feat.geometry.type === "MultiLineString") { + const lineList = feat.geometry.coordinates + for (const coordinates of lineList) { + + for (const rendering of startRenderings) { + const coordinate = coordinates[0] + addAsPoint(feat, rendering, coordinate) + } + for (const rendering of endRenderings) { + const coordinate = coordinates[coordinates.length - 1] + addAsPoint(feat, rendering, coordinate) + } + } + } + + + for (let i = 0; i < lineRenderObjects.length; i++) { + withIndex.push({ + ...feat, + lineRenderingIndex: i + }) + } + + } + } + return withIndex; + } + ); + + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts index b06aae6d2..fd98ad92e 100644 --- a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts @@ -1,8 +1,6 @@ import {UIEventSource} from "../../UIEventSource"; import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; -import {Utils} from "../../../Utils"; -import {Tiles} from "../../../Models/TileRange"; import {BBox} from "../../BBox"; export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { diff --git a/Logic/FeatureSource/Sources/WayHandlingApplyingFeatureSource.ts b/Logic/FeatureSource/Sources/WayHandlingApplyingFeatureSource.ts deleted file mode 100644 index cb36c4b21..000000000 --- a/Logic/FeatureSource/Sources/WayHandlingApplyingFeatureSource.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * This is the part of the pipeline which introduces extra points at the center of an area (but only if this is demanded by the wayhandling) - */ -import {UIEventSource} from "../../UIEventSource"; -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; -import {GeoOperations} from "../../GeoOperations"; -import {FeatureSourceForLayer} from "../FeatureSource"; - -export default class WayHandlingApplyingFeatureSource implements FeatureSourceForLayer { - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; - public readonly name; - public readonly layer; - - constructor(upstream: FeatureSourceForLayer) { - - this.name = "Wayhandling(" + upstream.name + ")"; - this.layer = upstream.layer - const layer = upstream.layer.layerDef; - - if (layer.wayHandling === LayerConfig.WAYHANDLING_DEFAULT) { - // We don't have to do anything fancy - // lets just wire up the upstream - this.features = upstream.features; - return; - } - - this.features = upstream.features.map( - features => { - if (features === undefined) { - return; - } - const newFeatures: { feature: any, freshness: Date }[] = []; - for (const f of features) { - const feat = f.feature; - - if (layer.wayHandling === LayerConfig.WAYHANDLING_DEFAULT) { - newFeatures.push(f); - continue; - } - - if (feat.geometry.type === "Point") { - newFeatures.push(f); - // feature is a point, nothing to do here - continue; - } - - // Create the copy - const centerPoint = GeoOperations.centerpoint(feat); - - newFeatures.push({feature: centerPoint, freshness: f.freshness}); - if (layer.wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) { - newFeatures.push(f); - } - } - return newFeatures; - } - ); - - } - -} \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index 5032f53ea..21aeec1c1 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -20,24 +20,28 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { if (source.geojsonSource === undefined) { throw "Invalid layer: geojsonSource expected" } - - const whitelistUrl = source.geojsonSource - .replace("{z}", ""+source.geojsonZoomLevel) - .replace("{x}_{y}.geojson", "overview.json") - .replace("{layer}",layer.layerDef.id) - + let whitelist = undefined - Utils.downloadJson(whitelistUrl).then( - json => { - const data = new Map>(); - for (const x in json) { - data.set(Number(x), new Set(json[x])) + if (source.geojsonSource.indexOf("{x}_{y}.geojson") > 0) { + + const whitelistUrl = source.geojsonSource + .replace("{z}", "" + source.geojsonZoomLevel) + .replace("{x}_{y}.geojson", "overview.json") + .replace("{layer}", layer.layerDef.id) + + Utils.downloadJson(whitelistUrl).then( + json => { + const data = new Map>(); + for (const x in json) { + data.set(Number(x), new Set(json[x])) + } + console.log("The whitelist is", data, "based on ", json, "from", whitelistUrl) + whitelist = data } - whitelist = data - } - ).catch(err => { - console.warn("No whitelist found for ", layer.layerDef.id, err) - }) + ).catch(err => { + console.warn("No whitelist found for ", layer.layerDef.id, err) + }) + } const seenIds = new Set(); const blackList = new UIEventSource(seenIds) @@ -45,14 +49,14 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { layer, source.geojsonZoomLevel, (zxy) => { - if(whitelist !== undefined){ + if (whitelist !== undefined) { const isWhiteListed = whitelist.get(zxy[1])?.has(zxy[2]) - if(!isWhiteListed){ + if (!isWhiteListed) { console.log("Not downloading tile", ...zxy, "as it is not on the whitelist") return undefined; } } - + const src = new GeoJsonSource( layer, zxy, diff --git a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts new file mode 100644 index 000000000..026e164f7 --- /dev/null +++ b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts @@ -0,0 +1,150 @@ +import TileHierarchy from "./TileHierarchy"; +import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource"; +import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject"; +import SimpleFeatureSource from "../Sources/SimpleFeatureSource"; +import FilteredLayer from "../../../Models/FilteredLayer"; +import {TagsFilter} from "../../Tags/TagsFilter"; +import OsmChangeAction from "../../Osm/Actions/OsmChangeAction"; +import StaticFeatureSource from "../Sources/StaticFeatureSource"; +import {OsmConnection} from "../../Osm/OsmConnection"; +import {GeoOperations} from "../../GeoOperations"; +import {Utils} from "../../../Utils"; +import {UIEventSource} from "../../UIEventSource"; +import {BBox} from "../../BBox"; +import FeaturePipeline from "../FeaturePipeline"; +import {Tag} from "../../Tags/Tag"; +import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"; +import {ChangeDescription} from "../../Osm/Actions/ChangeDescription"; +import CreateNewNodeAction from "../../Osm/Actions/CreateNewNodeAction"; +import ChangeTagAction from "../../Osm/Actions/ChangeTagAction"; +import {And} from "../../Tags/And"; + + +export default class FullNodeDatabaseSource implements TileHierarchy { + public readonly loadedTiles = new Map() + private readonly onTileLoaded: (tile: (Tiled & FeatureSourceForLayer)) => void; + private readonly layer: FilteredLayer + + constructor( + layer: FilteredLayer, + onTileLoaded: ((tile: Tiled & FeatureSourceForLayer) => void)) { + this.onTileLoaded = onTileLoaded + this.layer = layer; + if (this.layer === undefined) { + throw "Layer is undefined" + } + } + + /** + * Given a list of coordinates, will search already existing OSM-points to snap onto. + * Either the geometry will be moved OR the existing point will be moved, depending on configuration and tags. + * This requires the 'type_node'-layer to be activated + */ + public static MergePoints( + state: { + filteredLayers: UIEventSource, + featurePipeline: FeaturePipeline, + layoutToUse: LayoutConfig + }, + newGeometryLngLats: [number, number][], + configs: ConflationConfig[], + ) { + const typeNode = state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0] + if (typeNode === undefined) { + throw "Type Node layer is not defined. Add 'type_node' as layer to your layerconfig to use this feature" + } + + const bbox = new BBox(newGeometryLngLats) + const bbox_padded = bbox.pad(1.2) + const allNodes: any[] = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox).map(tile => tile.filter( + feature => bbox_padded.contains(GeoOperations.centerpointCoordinates(feature)) + ))) + // The strategy: for every point of the new geometry, we search a point that is closeby and matches + // If multiple options match, we choose the most optimal (aka closest) + + const maxDistance = Math.max(...configs.map(c => c.withinRangeOfM)) + for (const coordinate of newGeometryLngLats) { + + let closestNode = undefined; + let closestNodeDistance = undefined + for (const node of allNodes) { + const d = GeoOperations.distanceBetween(GeoOperations.centerpointCoordinates(node), coordinate) + if (d > maxDistance) { + continue + } + let matchesSomeConfig = false + for (const config of configs) { + if (d > config.withinRangeOfM) { + continue + } + if (!config.ifMatches.matchesProperties(node.properties)) { + continue + } + matchesSomeConfig = true; + } + if (!matchesSomeConfig) { + continue + } + if (closestNode === undefined || closestNodeDistance > d) { + closestNode = node; + closestNodeDistance = d; + } + } + + + } + + } + + + + public handleOsmJson(osmJson: any, tileId: number) { + + const allObjects = OsmObject.ParseObjects(osmJson.elements) + const nodesById = new Map() + + for (const osmObj of allObjects) { + if (osmObj.type !== "node") { + continue + } + const osmNode = osmObj; + nodesById.set(osmNode.id, osmNode) + } + + const parentWaysByNodeId = new Map() + for (const osmObj of allObjects) { + if (osmObj.type !== "way") { + continue + } + const osmWay = osmObj; + for (const nodeId of osmWay.nodes) { + + if (!parentWaysByNodeId.has(nodeId)) { + parentWaysByNodeId.set(nodeId, []) + } + parentWaysByNodeId.get(nodeId).push(osmWay) + } + } + parentWaysByNodeId.forEach((allWays, nodeId) => { + nodesById.get(nodeId).tags["parent_ways"] = JSON.stringify(allWays.map(w => w.tags)) + }) + const now = new Date() + const asGeojsonFeatures = Array.from(nodesById.values()).map(osmNode => ({ + feature: osmNode.asGeoJson(), freshness: now + })) + + const featureSource = new SimpleFeatureSource(this.layer, tileId) + featureSource.features.setData(asGeojsonFeatures) + this.loadedTiles.set(tileId, featureSource) + this.onTileLoaded(featureSource) + + } + + +} + +export interface ConflationConfig { + withinRangeOfM: number, + ifMatches: TagsFilter, + mode: "reuse_osm_point" | "move_osm_point" +} \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts index a4bdc8ca5..e6a5ee814 100644 --- a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts @@ -30,6 +30,8 @@ export default class OsmFeatureSource { }; public readonly downloadedTiles = new Set() private readonly allowedTags: TagsFilter; + + public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = [] constructor(options: { handleTile: (tile: FeatureSourceForLayer & Tiled) => void; @@ -66,7 +68,7 @@ export default class OsmFeatureSource { console.log("Tile download", Tiles.tile_from_index(neededTile).join("/"), "started") self.downloadedTiles.add(neededTile) self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => { - console.log("Tile ", Tiles.tile_from_index(neededTile).join("/"), "loaded") + console.debug("Tile ", Tiles.tile_from_index(neededTile).join("/"), "loaded") }) } } catch (e) { @@ -94,11 +96,11 @@ export default class OsmFeatureSource { try { console.log("Attempting to get tile", z, x, y, "from the osm api") - const osmXml = await Utils.download(url, {"accept": "application/xml"}) + const osmJson = await Utils.downloadJson(url) try { - const parsed = new DOMParser().parseFromString(osmXml, "text/xml"); - console.log("Got tile", z, x, y, "from the osm api") - const geojson = OsmToGeoJson.default(parsed, + console.debug("Got tile", z, x, y, "from the osm api") + this.rawDataHandlers.forEach(handler => handler(osmJson, Tiles.tile_index(z, x, y))) + const geojson = OsmToGeoJson.default(osmJson, // @ts-ignore { flatProperties: true @@ -108,10 +110,8 @@ export default class OsmFeatureSource { // We only keep what is needed geojson.features = geojson.features.filter(feature => this.allowedTags.matchesProperties(feature.properties)) - geojson.features.forEach(f => f.properties["_backend"] = this._backend) - console.log("Tile geojson:", z, x, y, "is", geojson) const index = Tiles.tile_index(z, x, y); new PerLayerFeatureSourceSplitter(this.filteredLayers, this.handleTile, diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 87716003e..f94f9944a 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -226,7 +226,7 @@ export class GeoOperations { /** * Generates the closest point on a way from a given point - * + * * The properties object will contain three values: // - `index`: closest point was found on nth line part, // - `dist`: distance between pt and the closest point (in kilometer), @@ -235,6 +235,13 @@ export class GeoOperations { * @param point Point defined as [lon, lat] */ public static nearestPoint(way, point: [number, number]) { + if(way.geometry.type === "Polygon"){ + way = {...way} + way.geometry = {...way.geometry} + way.geometry.type = "LineString" + way.geometry.coordinates = way.geometry.coordinates[0] + } + return turf.nearestPointOnLine(way, point, {units: "kilometers"}); } @@ -283,6 +290,34 @@ export class GeoOperations { return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n") } + + private static readonly _earthRadius = 6378137; + private static readonly _originShift = 2 * Math.PI * GeoOperations._earthRadius / 2; + + //Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913 + public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] { + const lon = lonLat[0]; + const lat = lonLat[1]; + const x = lon * GeoOperations._originShift / 180; + let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); + y = y * GeoOperations._originShift / 180; + return [x, y]; + } + +//Converts XY point from (Spherical) Web Mercator EPSG:3785 (unofficially EPSG:900913) to lat/lon in WGS84 Datum + public static Convert900913ToWgs84(lonLat: [number, number]): [number, number] { + const lon = lonLat[0] + const lat = lonLat[1] + const x = 180 * lon / GeoOperations._originShift; + let y = 180 * lat / GeoOperations._originShift; + y = 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2); + return [x, y]; + } + + public static GeoJsonToWGS84(geojson){ + return turf.toWgs84(geojson) + } + /** * 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 diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 83b85f34a..a6e3cc44b 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -18,6 +18,8 @@ export default class MetaTagging { /** * This method (re)calculates all metatags and calculated tags on every given object. * The given features should be part of the given layer + * + * Returns true if at least one feature has changed properties */ public static addMetatags(features: { feature: any; freshness: Date }[], params: ExtraFuncParams, @@ -25,7 +27,7 @@ export default class MetaTagging { options?: { includeDates?: true | boolean, includeNonDates?: true | boolean - }) { + }): boolean { if (features === undefined || features.length === 0) { return; @@ -48,6 +50,7 @@ export default class MetaTagging { // The calculated functions - per layer - which add the new keys const layerFuncs = this.createRetaggingFunc(layer) + let atLeastOneFeatureChanged = false; for (let i = 0; i < features.length; i++) { const ff = features[i]; @@ -95,8 +98,10 @@ export default class MetaTagging { if (somethingChanged) { State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping() + atLeastOneFeatureChanged = true } } + return atLeastOneFeatureChanged } diff --git a/Logic/Osm/Actions/ChangeDescription.ts b/Logic/Osm/Actions/ChangeDescription.ts index 44804bc32..c8a262d6f 100644 --- a/Logic/Osm/Actions/ChangeDescription.ts +++ b/Logic/Osm/Actions/ChangeDescription.ts @@ -16,7 +16,7 @@ export interface ChangeDescription { /** * The type of the change */ - changeType: "answer" | "create" | "split" | "delete" | "move" | string + changeType: "answer" | "create" | "split" | "delete" | "move" | "import" | string | null /** * THe motivation for the change, e.g. 'deleted because does not exist anymore' */ @@ -51,7 +51,8 @@ export interface ChangeDescription { lat: number, lon: number } | { - // Coordinates are only used for rendering. They should be LAT, LON + /* Coordinates are only used for rendering. They should be LON, LAT + * */ coordinates: [number, number][] nodes: number[], } | { diff --git a/Logic/Osm/Actions/ChangeTagAction.ts b/Logic/Osm/Actions/ChangeTagAction.ts index 00e9b001e..862d36629 100644 --- a/Logic/Osm/Actions/ChangeTagAction.ts +++ b/Logic/Osm/Actions/ChangeTagAction.ts @@ -11,7 +11,7 @@ export default class ChangeTagAction extends OsmChangeAction { constructor(elementId: string, tagsFilter: TagsFilter, currentTags: any, meta: { theme: string, - changeType: "answer" | "soft-delete" | "add-image" + changeType: "answer" | "soft-delete" | "add-image" | string }) { super(); this._elementId = elementId; @@ -27,11 +27,16 @@ export default class ChangeTagAction extends OsmChangeAction { const key = kv.k; const value = kv.v; if (key === undefined || key === null) { - console.log("Invalid key"); + console.error("Invalid key:", key); return undefined; } if (value === undefined || value === null) { - console.log("Invalid value for ", key); + console.error("Invalid value for ", key,":", value); + return undefined; + } + + if(typeof value !== "string"){ + console.error("Invalid value for ", key, "as it is not a string:", value) return undefined; } diff --git a/Logic/Osm/Actions/CreateNewNodeAction.ts b/Logic/Osm/Actions/CreateNewNodeAction.ts index e644eb2c6..b8e0f0171 100644 --- a/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -8,20 +8,29 @@ import {GeoOperations} from "../../GeoOperations"; export default class CreateNewNodeAction extends OsmChangeAction { + /** + * Maps previously created points onto their assigned ID, to reuse the point if uplaoded + * "lat,lon" --> id + */ + private static readonly previouslyCreatedPoints = new Map() public newElementId: string = undefined + public newElementIdNumber: number = undefined private readonly _basicTags: Tag[]; private readonly _lat: number; private readonly _lon: number; private readonly _snapOnto: OsmWay; private readonly _reusePointDistance: number; private meta: { changeType: "create" | "import"; theme: string }; + private readonly _reusePreviouslyCreatedPoint: boolean; constructor(basicTags: Tag[], lat: number, lon: number, options: { - snapOnto?: OsmWay, - reusePointWithinMeters?: number, - theme: string, changeType: "create" | "import" }) { + allowReuseOfPreviouslyCreatedPoints?: boolean, + snapOnto?: OsmWay, + reusePointWithinMeters?: number, + theme: string, changeType: "create" | "import" | null + }) { super() this._basicTags = basicTags; this._lat = lat; @@ -31,18 +40,47 @@ export default class CreateNewNodeAction extends OsmChangeAction { } this._snapOnto = options?.snapOnto; this._reusePointDistance = options?.reusePointWithinMeters ?? 1 + this._reusePreviouslyCreatedPoint = options?.allowReuseOfPreviouslyCreatedPoints ?? (basicTags.length === 0) this.meta = { theme: options.theme, changeType: options.changeType } } + public static registerIdRewrites(mappings: Map) { + const toAdd: [string, number][] = [] + + this.previouslyCreatedPoints.forEach((oldId, key) => { + if (!mappings.has("node/" + oldId)) { + return; + } + + const newId = Number(mappings.get("node/" + oldId).substr("node/".length)) + toAdd.push([key, newId]) + }) + for (const [key, newId] of toAdd) { + CreateNewNodeAction.previouslyCreatedPoints.set(key, newId) + } + } + async CreateChangeDescriptions(changes: Changes): Promise { + if (this._reusePreviouslyCreatedPoint) { + + const key = this._lat + "," + this._lon + const prev = CreateNewNodeAction.previouslyCreatedPoints + if (prev.has(key)) { + this.newElementIdNumber = prev.get(key) + this.newElementId = "node/" + this.newElementIdNumber + return [] + } + } + + const id = changes.getNewID() const properties = { id: "node/" + id } - this.newElementId = "node/" + id + this.setElementId(id) for (const kv of this._basicTags) { if (typeof kv.value !== "string") { throw "Invalid value: don't use a regex in a preset" @@ -84,8 +122,7 @@ export default class CreateNewNodeAction extends OsmChangeAction { } if (reusedPointId !== undefined) { console.log("Reusing an existing point:", reusedPointId) - this.newElementId = "node/" + reusedPointId - + this.setElementId(reusedPointId) return [{ tags: new And(this._basicTags).asChange(properties), type: "node", @@ -112,10 +149,20 @@ export default class CreateNewNodeAction extends OsmChangeAction { coordinates: locations, nodes: ids }, - meta:this.meta + meta: this.meta } ] } + private setElementId(id: number) { + this.newElementIdNumber = id; + this.newElementId = "node/"+id + if (!this._reusePreviouslyCreatedPoint) { + return + } + const key = this._lat + "," + this._lon + CreateNewNodeAction.previouslyCreatedPoints.set(key, id) + } + } \ No newline at end of file diff --git a/Logic/Osm/Actions/CreateNewWayAction.ts b/Logic/Osm/Actions/CreateNewWayAction.ts new file mode 100644 index 000000000..48b7ec7fb --- /dev/null +++ b/Logic/Osm/Actions/CreateNewWayAction.ts @@ -0,0 +1,78 @@ +import {ChangeDescription} from "./ChangeDescription"; +import OsmChangeAction from "./OsmChangeAction"; +import {Changes} from "../Changes"; +import {Tag} from "../../Tags/Tag"; +import CreateNewNodeAction from "./CreateNewNodeAction"; +import {And} from "../../Tags/And"; + +export default class CreateNewWayAction extends OsmChangeAction { + public newElementId: string = undefined + private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[]; + private readonly tags: Tag[]; + private readonly _options: { + theme: string + }; + + + /*** + * Creates a new way to upload to OSM + * @param tags: the tags to apply to the way + * @param coordinates: the coordinates. Might have a nodeId, in this case, this node will be used + * @param options + */ + constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[], + options: { + theme: string + }) { + super() + this.coordinates = coordinates; + this.tags = tags; + this._options = options; + } + + protected async CreateChangeDescriptions(changes: Changes): Promise { + + const newElements: ChangeDescription[] = [] + + const pointIds: number[] = [] + for (const coordinate of this.coordinates) { + if (coordinate.nodeId !== undefined) { + pointIds.push(coordinate.nodeId) + continue + } + + const newPoint = new CreateNewNodeAction([], coordinate.lat, coordinate.lon, { + allowReuseOfPreviouslyCreatedPoints: true, + changeType: null, + theme: this._options.theme + }) + await changes.applyAction(newPoint) + pointIds.push(newPoint.newElementIdNumber) + } + + // We have all created (or reused) all the points! + // Time to create the actual way + + + const id = changes.getNewID() + + const newWay = { + id, + type: "way", + meta: { + theme: this._options.theme, + changeType: "import" + }, + tags: new And(this.tags).asChange({}), + changes: { + nodes: pointIds, + coordinates: this.coordinates.map(c => [c.lon, c.lat]) + } + } + newElements.push(newWay) + this.newElementId = "way/" + id + return newElements + } + + +} \ No newline at end of file diff --git a/Logic/Osm/Actions/ReplaceGeometryAction.ts b/Logic/Osm/Actions/ReplaceGeometryAction.ts new file mode 100644 index 000000000..72195ff7f --- /dev/null +++ b/Logic/Osm/Actions/ReplaceGeometryAction.ts @@ -0,0 +1,232 @@ +import OsmChangeAction from "./OsmChangeAction"; +import {Changes} from "../Changes"; +import {ChangeDescription} from "./ChangeDescription"; +import {Tag} from "../../Tags/Tag"; +import FeatureSource from "../../FeatureSource/FeatureSource"; +import {OsmNode, OsmObject, OsmWay} from "../OsmObject"; +import {GeoOperations} from "../../GeoOperations"; +import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"; +import CreateNewNodeAction from "./CreateNewNodeAction"; +import ChangeTagAction from "./ChangeTagAction"; +import {And} from "../../Tags/And"; +import {Utils} from "../../../Utils"; +import {OsmConnection} from "../OsmConnection"; + +export default class ReplaceGeometryAction extends OsmChangeAction { + private readonly feature: any; + private readonly state: { + osmConnection: OsmConnection + }; + private readonly wayToReplaceId: string; + private readonly theme: string; + private readonly targetCoordinates: [number, number][]; + private readonly newTags: Tag[] | undefined; + + constructor( + state: { + osmConnection: OsmConnection + }, + feature: any, + wayToReplaceId: string, + options: { + theme: string, + newTags?: Tag[] + } + ) { + super(); + this.state = state; + this.feature = feature; + this.wayToReplaceId = wayToReplaceId; + this.theme = options.theme; + + const geom = this.feature.geometry + let coordinates: [number, number][] + if (geom.type === "LineString") { + coordinates = geom.coordinates + } else if (geom.type === "Polygon") { + coordinates = geom.coordinates[0] + } + this.targetCoordinates = coordinates + this.newTags = options.newTags + } + + public async GetPreview(): Promise { + const {closestIds, allNodesById} = await this.GetClosestIds(); + const preview = closestIds.map((newId, i) => { + if (newId === undefined) { + return { + type: "Feature", + properties: { + "newpoint": "yes", + "id": "replace-geometry-move-" + i + }, + geometry: { + type: "Point", + coordinates: this.targetCoordinates[i] + } + }; + } + const origPoint = allNodesById.get(newId).centerpoint() + return { + type: "Feature", + properties: { + "move": "yes", + "osm-id": newId, + "id": "replace-geometry-move-" + i + }, + geometry: { + type: "LineString", + coordinates: [[origPoint[1], origPoint[0]], this.targetCoordinates[i]] + } + }; + }) + return new StaticFeatureSource(preview, false) + + } + + protected async CreateChangeDescriptions(changes: Changes): Promise { + + const allChanges: ChangeDescription[] = [] + const actualIdsToUse: number[] = [] + + const {closestIds, osmWay} = await this.GetClosestIds() + + for (let i = 0; i < closestIds.length; i++) { + const closestId = closestIds[i]; + const [lon, lat] = this.targetCoordinates[i] + if (closestId === undefined) { + + const newNodeAction = new CreateNewNodeAction( + [], + lat, lon, + { + allowReuseOfPreviouslyCreatedPoints: true, + theme: this.theme, changeType: null + }) + const changeDescr = await newNodeAction.CreateChangeDescriptions(changes) + allChanges.push(...changeDescr) + actualIdsToUse.push(newNodeAction.newElementIdNumber) + + } else { + const change = { + id: closestId, + type: "node", + meta: { + theme: this.theme, + changeType: "move" + }, + changes: {lon, lat} + } + actualIdsToUse.push(closestId) + allChanges.push(change) + } + } + + + if (this.newTags !== undefined && this.newTags.length > 0) { + const addExtraTags = new ChangeTagAction( + this.wayToReplaceId, + new And(this.newTags), + osmWay.tags, { + theme: this.theme, + changeType: "conflation" + } + ) + allChanges.push(...await addExtraTags.CreateChangeDescriptions(changes)) + } + + // AT the very last: actually change the nodes of the way! + allChanges.push({ + type: "way", + id: osmWay.id, + changes: { + nodes: actualIdsToUse, + coordinates: this.targetCoordinates + }, + meta: { + theme: this.theme, + changeType: "conflation" + } + }) + + + return allChanges + } + + /** + * For 'this.feature`, gets a corresponding closest node that alreay exsists + * @constructor + * @private + */ + private async GetClosestIds(): Promise<{ closestIds: number[], allNodesById: Map, osmWay: OsmWay }> { + // TODO FIXME: cap move length on points which are embedded into other ways (ev. disconnect them) + // TODO FIXME: if a new point has to be created, snap to already existing ways + // TODO FIXME: reuse points if they are the same in the target coordinates + const splitted = this.wayToReplaceId.split("/"); + const type = splitted[0]; + const idN = Number(splitted[1]); + if (idN < 0 || type !== "way") { + throw "Invalid ID to conflate: " + this.wayToReplaceId + } + const url = `${this.state.osmConnection._oauth_config.url}/api/0.6/${this.wayToReplaceId}/full`; + const rawData = await Utils.downloadJsonCached(url, 1000) + const parsed = OsmObject.ParseObjects(rawData.elements); + const allNodesById = new Map() + const allNodes = parsed.filter(o => o.type === "node") + for (const node of allNodes) { + allNodesById.set(node.id, node) + } + + + /** + * Allright! We know all the nodes of the original way and all the nodes of the target coordinates. + * For each of the target coordinates, we search the closest, already existing point and reuse this point + */ + + const closestIds = [] + const distances = [] + for (const target of this.targetCoordinates) { + let closestDistance = undefined + let closestId = undefined; + for (const osmNode of allNodes) { + + const cp = osmNode.centerpoint() + const d = GeoOperations.distanceBetween(target, [cp[1], cp[0]]) + if (closestId === undefined || closestDistance > d) { + closestId = osmNode.id + closestDistance = d + } + } + closestIds.push(closestId) + distances.push(closestDistance) + } + + // Next step: every closestId can only occur once in the list + for (let i = 0; i < closestIds.length; i++) { + const closestId = closestIds[i] + for (let j = i + 1; j < closestIds.length; j++) { + const otherClosestId = closestIds[j] + if (closestId !== otherClosestId) { + continue + } + // We have two occurences of 'closestId' - we only keep the closest instance! + const di = distances[i] + const dj = distances[j] + if (di < dj) { + closestIds[j] = undefined + } else { + closestIds[i] = undefined + } + } + } + + + const osmWay = parsed[parsed.length - 1] + if (osmWay.type !== "way") { + throw "WEIRD: expected an OSM-way as last element here!" + } + return {closestIds, allNodesById, osmWay}; + } + + +} \ No newline at end of file diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 7faa23494..72e5434c5 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -6,6 +6,8 @@ import OsmChangeAction from "./Actions/OsmChangeAction"; import {ChangeDescription} from "./Actions/ChangeDescription"; import {Utils} from "../../Utils"; import {LocalStorageSource} from "../Web/LocalStorageSource"; +import SimpleMetaTagger from "../SimpleMetaTagger"; +import CreateNewNodeAction from "./Actions/CreateNewNodeAction"; /** * Handles all changes made to OSM. @@ -13,21 +15,21 @@ import {LocalStorageSource} from "../Web/LocalStorageSource"; */ export class Changes { - - private _nextId: number = -1; // Newly assigned ID's are negative public readonly name = "Newly added features" /** * All the newly created features as featureSource + all the modified features */ public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]); - public readonly pendingChanges: UIEventSource = LocalStorageSource.GetParsed("pending-changes", []) public readonly allChanges = new UIEventSource(undefined) + private _nextId: number = -1; // Newly assigned ID's are negative private readonly isUploading = new UIEventSource(false); private readonly previouslyCreated: OsmObject[] = [] + private readonly _leftRightSensitive: boolean; - constructor() { + constructor(leftRightSensitive: boolean = false) { + this._leftRightSensitive = leftRightSensitive; // We keep track of all changes just as well this.allChanges.setData([...this.pendingChanges.data]) // If a pending change contains a negative ID, we save that @@ -111,16 +113,44 @@ export class Changes { }) } + public async applyAction(action: OsmChangeAction): Promise { + this.applyChanges(await action.Perform(this)) + } + + public async applyActions(actions: OsmChangeAction[]) { + for (const action of actions) { + await this.applyAction(action) + } + } + + public applyChanges(changes: ChangeDescription[]) { + console.log("Received changes:", changes) + this.pendingChanges.data.push(...changes); + this.pendingChanges.ping(); + this.allChanges.data.push(...changes) + this.allChanges.ping() + } + + public registerIdRewrites(mappings: Map): void { + CreateNewNodeAction.registerIdRewrites(mappings) + } + + /** * UPload the selected changes to OSM. * Returns 'true' if successfull and if they can be removed * @param pending * @private */ - private async flushSelectChanges(pending: ChangeDescription[]): Promise{ + private async flushSelectChanges(pending: ChangeDescription[]): Promise { const self = this; const neededIds = Changes.GetNeededIds(pending) const osmObjects = await Promise.all(neededIds.map(id => OsmObject.DownloadObjectAsync(id))); + + if (this._leftRightSensitive) { + osmObjects.forEach(obj => SimpleMetaTagger.removeBothTagging(obj.tags)) + } + console.log("Got the fresh objects!", osmObjects, "pending: ", pending) const changes: { newObjects: OsmObject[], @@ -129,35 +159,38 @@ export class Changes { } = self.CreateChangesetObjects(pending, osmObjects) if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) { console.log("No changes to be made") - return true + return true } const meta = pending[0].meta - - const perType = Array.from(Utils.Hist(pending.map(descr => descr.meta.changeType)), ([key, count]) => ({ - key: key, - value: count, - aggregate: true - })) + + const perType = Array.from( + Utils.Hist(pending.filter(descr => descr.meta.changeType !== undefined && descr.meta.changeType !== null) + .map(descr => descr.meta.changeType)), ([key, count]) => ( + { + key: key, + value: count, + aggregate: true + })) const motivations = pending.filter(descr => descr.meta.specialMotivation !== undefined) .map(descr => ({ - key: descr.meta.changeType+":"+descr.type+"/"+descr.id, - value: descr.meta.specialMotivation + key: descr.meta.changeType + ":" + descr.type + "/" + descr.id, + value: descr.meta.specialMotivation })) const metatags = [{ key: "comment", - value: "Adding data with #MapComplete for theme #"+meta.theme + value: "Adding data with #MapComplete for theme #" + meta.theme }, { - key:"theme", - value:meta.theme + key: "theme", + value: meta.theme }, ...perType, ...motivations ] - + await State.state.osmConnection.changesetHandler.UploadChangeset( - (csId) => Changes.createChangesetFor(""+csId, changes), + (csId) => Changes.createChangesetFor("" + csId, changes), metatags ) @@ -170,27 +203,27 @@ export class Changes { try { // At last, we build the changeset and upload const pending = self.pendingChanges.data; - + const pendingPerTheme = new Map() for (const changeDescription of pending) { const theme = changeDescription.meta.theme - if(!pendingPerTheme.has(theme)){ + if (!pendingPerTheme.has(theme)) { pendingPerTheme.set(theme, []) } pendingPerTheme.get(theme).push(changeDescription) } - - const successes = await Promise.all(Array.from(pendingPerTheme, ([key , value]) => value) + + const successes = await Promise.all(Array.from(pendingPerTheme, ([key, value]) => value) .map(async pendingChanges => { - try{ + try { return await self.flushSelectChanges(pendingChanges); - }catch(e){ - console.error("Could not upload some changes:",e) + } catch (e) { + console.error("Could not upload some changes:", e) return false } })) - - if(!successes.some(s => s == false)){ + + if (!successes.some(s => s == false)) { // All changes successfull, we clear the data! this.pendingChanges.setData([]); } @@ -198,22 +231,13 @@ export class Changes { } catch (e) { console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e) self.pendingChanges.setData([]) - }finally { + } finally { self.isUploading.setData(false) } } - public async applyAction(action: OsmChangeAction): Promise { - const changes = await action.Perform(this) - console.log("Received changes:", changes) - this.pendingChanges.data.push(...changes); - this.pendingChanges.ping(); - this.allChanges.data.push(...changes) - this.allChanges.ping() - } - private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { newObjects: OsmObject[], modifiedObjects: OsmObject[] @@ -365,8 +389,4 @@ export class Changes { return result } - - public registerIdRewrites(mappings: Map): void { - - } } \ No newline at end of file diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index faf1ee79d..cfc9c5234 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -206,7 +206,7 @@ export abstract class OsmObject { return result; } - private static ParseObjects(elements: any[]): OsmObject[] { + public static ParseObjects(elements: any[]): OsmObject[] { const objects: OsmObject[] = []; const allNodes: Map = new Map() diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 7d42e9d01..cbe30c726 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -26,7 +26,7 @@ export default class SimpleMetaTagger { "_last_edit:changeset", "_last_edit:timestamp", "_version_number", - "_backend"], + "_backend"], doc: "Information about the last edit of this object." }, (feature) => {/*Note: also called by 'UpdateTagsFromOsmAPI'*/ @@ -67,17 +67,110 @@ export default class SimpleMetaTagger { private static layerInfo = new SimpleMetaTagger( { doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.", - keys:["_layer"], + keys: ["_layer"], includesDates: false, }, (feature, freshness, layer) => { - if(feature.properties._layer === layer.id){ + if (feature.properties._layer === layer.id) { return false; } feature.properties._layer = layer.id return true; } ) + + /** + * Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme. + * These changes are performed in-place. + * + * Returns 'true' is at least one change has been made + * @param tags + */ + public static removeBothTagging(tags: any): boolean{ + let somethingChanged = false + /** + * Sets the key onto the properties (but doesn't overwrite if already existing) + */ + function set(k, value) { + if (tags[k] === undefined || tags[k] === "") { + tags[k] = value + somethingChanged = true + } + } + + if (tags["sidewalk"]) { + + const v = tags["sidewalk"] + switch (v) { + case "none": + case "no": + set("sidewalk:left", "no"); + set("sidewalk:right", "no"); + break + case "both": + set("sidewalk:left", "yes"); + set("sidewalk:right", "yes"); + break; + case "left": + set("sidewalk:left", "yes"); + set("sidewalk:right", "no"); + break; + case "right": + set("sidewalk:left", "no"); + set("sidewalk:right", "yes"); + break; + default: + set("sidewalk:left", v); + set("sidewalk:right", v); + break; + } + delete tags["sidewalk"] + somethingChanged = true + } + + + const regex = /\([^:]*\):both:\(.*\)/ + for (const key in tags) { + const v = tags[key] + if (key.endsWith(":both")) { + const strippedKey = key.substring(0, key.length - ":both".length) + set(strippedKey + ":left", v) + set(strippedKey + ":right", v) + delete tags[key] + continue + } + + const match = key.match(regex) + if (match !== null) { + const strippedKey = match[1] + const property = match[1] + set(strippedKey + ":left:" + property, v) + set(strippedKey + ":right:" + property, v) + console.log("Left-right rewritten " + key) + delete tags[key] + } + } + + + return somethingChanged + } + + private static noBothButLeftRight = new SimpleMetaTagger( + { + keys: ["sidewalk:left", "sidewalk:right", "generic_key:left:property", "generic_key:right:property"], + doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined", + includesDates: false, + cleanupRetagger: true + }, + ((feature, state, layer) => { + + if(!layer.lineRendering.some(lr => lr.leftRightSensitive)){ + return; + } + + return SimpleMetaTagger.removeBothTagging(feature.properties) + }) + ) private static surfaceArea = new SimpleMetaTagger( { keys: ["_surface", "_surface:ha"], @@ -85,12 +178,12 @@ export default class SimpleMetaTagger { isLazy: true }, (feature => { - + Object.defineProperty(feature.properties, "_surface", { enumerable: false, configurable: true, get: () => { - const sqMeters = ""+ GeoOperations.surfaceAreaInSqMeters(feature); + const sqMeters = "" + GeoOperations.surfaceAreaInSqMeters(feature); delete feature.properties["_surface"] feature.properties["_surface"] = sqMeters; return sqMeters @@ -108,7 +201,7 @@ export default class SimpleMetaTagger { return sqMetersHa } }) - + return true; }) ); @@ -219,8 +312,8 @@ export default class SimpleMetaTagger { // isOpen is irrelevant return false } - - Object.defineProperty(feature.properties, "_isOpen",{ + + Object.defineProperty(feature.properties, "_isOpen", { enumerable: false, configurable: true, get: () => { @@ -247,7 +340,7 @@ export default class SimpleMetaTagger { if (oldNextChange > (new Date()).getTime() && tags["_isOpen:oldvalue"] === tags["opening_hours"] - && tags["_isOpen"] !== undefined) { + && tags["_isOpen"] !== undefined) { // Already calculated and should not yet be triggered return false; } @@ -354,7 +447,8 @@ export default class SimpleMetaTagger { SimpleMetaTagger.isOpen, SimpleMetaTagger.directionSimplified, SimpleMetaTagger.currentTime, - SimpleMetaTagger.objectMetaInfo + SimpleMetaTagger.objectMetaInfo, + SimpleMetaTagger.noBothButLeftRight ]; public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy) @@ -365,22 +459,24 @@ export default class SimpleMetaTagger { public readonly isLazy: boolean; public readonly includesDates: boolean public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean; - + /*** * A function that adds some extra data to a feature * @param docs: what does this extra data do? * @param f: apply the changes. Returns true if something changed */ - constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean }, + constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean }, f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) { this.keys = docs.keys; this.doc = docs.doc; this.isLazy = docs.isLazy this.applyMetaTagsOnFeature = f; this.includesDates = docs.includesDates ?? false; - for (const key of docs.keys) { - if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) { - throw `Incorrect metakey ${key}: it should start with underscore (_)` + if (!docs.cleanupRetagger) { + for (const key of docs.keys) { + if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) { + throw `Incorrect metakey ${key}: it should start with underscore (_)` + } } } } diff --git a/Logic/State/ElementsState.ts b/Logic/State/ElementsState.ts index 241badaa2..637482e60 100644 --- a/Logic/State/ElementsState.ts +++ b/Logic/State/ElementsState.ts @@ -15,7 +15,7 @@ import TitleHandler from "../Actors/TitleHandler"; /** * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc */ -export default class ElementsState extends FeatureSwitchState{ +export default class ElementsState extends FeatureSwitchState { /** The mapping from id -> UIEventSource @@ -24,7 +24,7 @@ export default class ElementsState extends FeatureSwitchState{ /** THe change handler */ - public changes: Changes = new Changes(); + public changes: Changes; /** The latest element that was selected @@ -34,7 +34,7 @@ export default class ElementsState extends FeatureSwitchState{ "Selected element" ); - + /** * The map location: currently centered lat, lon and zoom */ @@ -48,6 +48,9 @@ export default class ElementsState extends FeatureSwitchState{ constructor(layoutToUse: LayoutConfig) { super(layoutToUse); + + this.changes = new Changes(layoutToUse?.isLeftRightSensitive() ?? false) + { // -- Location control initialization const zoom = UIEventSource.asFloat( @@ -84,10 +87,10 @@ export default class ElementsState extends FeatureSwitchState{ lon.setData(latlonz.lon); }); } - + new ChangeToElementsActor(this.changes, this.allElements) new PendingChangesUploader(this.changes, this.selectedElement); new TitleHandler(this); - + } } \ No newline at end of file diff --git a/Logic/State/FeatureSwitchState.ts b/Logic/State/FeatureSwitchState.ts index 0f5df51f7..d02abe270 100644 --- a/Logic/State/FeatureSwitchState.ts +++ b/Logic/State/FeatureSwitchState.ts @@ -37,7 +37,7 @@ export default class FeatureSwitchState { public readonly osmApiTileSize: UIEventSource; public readonly backgroundLayerId: UIEventSource; - protected constructor(layoutToUse: LayoutConfig) { + public constructor(layoutToUse: LayoutConfig) { this.layoutToUse = layoutToUse; diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 00a05dd61..8939149f5 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -14,6 +14,7 @@ import {QueryParameters} from "../Web/QueryParameters"; import * as personal from "../../assets/themes/personal/personal.json"; import FilterConfig from "../../Models/ThemeConfig/FilterConfig"; import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"; +import {Coord} from "@turf/turf"; /** * Contains all the leaflet-map related state @@ -44,13 +45,7 @@ export default class MapState extends UserRelatedState { /** * The location as delivered by the GPS */ - public currentGPSLocation: UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }> = new UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }>(undefined); + public currentGPSLocation: UIEventSource = new UIEventSource(undefined); public readonly mainMapObject: BaseUIElement & MinimapObj; diff --git a/Logic/Tags/TagUtils.ts b/Logic/Tags/TagUtils.ts index 06949c570..0ff6896bb 100644 --- a/Logic/Tags/TagUtils.ts +++ b/Logic/Tags/TagUtils.ts @@ -19,16 +19,6 @@ export class TagUtils { [">", (a, b) => a > b], ] - static ApplyTemplate(template: string, tags: any): string { - for (const k in tags) { - while (template.indexOf("{" + k + "}") >= 0) { - const escaped = tags[k].replace(//g, '>'); - template = template.replace("{" + k + "}", escaped); - } - } - return template; - } - static KVtoProperties(tags: Tag[]): any { const properties = {}; for (const tag of tags) { @@ -37,6 +27,14 @@ export class TagUtils { return properties; } + static changeAsProperties(kvs : {k: string, v: string}[]): any { + const tags = {} + for (const kv of kvs) { + tags[kv.k] = kv.v + } + return tags + } + /** * Given two hashes of {key --> values[]}, makes sure that every neededTag is present in availableTags */ diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 571c74f8e..8051128ce 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -109,6 +109,20 @@ export class UIEventSource { promise?.catch(err => src.setData({error: err})) return src } + + public withEqualityStabilized(comparator: (t:T | undefined, t1:T | undefined) => boolean): UIEventSource{ + let oldValue = undefined; + return this.map(v => { + if(v == oldValue){ + return oldValue + } + if(comparator(oldValue, v)){ + return oldValue + } + oldValue = v; + return v; + }) + } /** * Given a UIEVentSource with a list, returns a new UIEventSource which is only updated if the _contents_ of the list are different. diff --git a/Models/Constants.ts b/Models/Constants.ts index 6405a34b6..8d7dab35e 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import {Utils} from "../Utils"; export default class Constants { - public static vNumber = "0.11.2"; + public static vNumber = "0.12.1-beta"; public static ImgurApiKey = '7070e7167f0a25a' public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2' public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index e13734c8a..1d0e7f8fd 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -4,6 +4,8 @@ import FilterConfigJson from "./FilterConfigJson"; import {DeleteConfigJson} from "./DeleteConfigJson"; import UnitConfigJson from "./UnitConfigJson"; import MoveConfigJson from "./MoveConfigJson"; +import PointRenderingConfigJson from "./PointRenderingConfigJson"; +import LineRenderingConfigJson from "./LineRenderingConfigJson"; /** * Configuration for a single layer @@ -53,6 +55,8 @@ export interface LayerConfigJson { * source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14} * 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 * + * Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max} + * Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this * * Note that both geojson-options might set a flag 'isOsmCache' indicating that the data originally comes from OSM too * @@ -61,7 +65,7 @@ export interface LayerConfigJson { * While still supported, this is considered deprecated */ source: ({ osmTags: AndOrTagConfigJson | string, overpassScript?: string } | - { osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean }) & ({ + { osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean, mercatorCrs?: boolean }) & ({ /** * The maximum amount of seconds that a tile is allowed to linger in the cache */ @@ -124,72 +128,8 @@ export interface LayerConfigJson { */ titleIcons?: (string | TagRenderingConfigJson)[]; - /** - * The icon for an element. - * Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets. - * - * The result of the icon is rendered as follows: - * the resulting string is interpreted as a _list_ of items, separated by ";". The bottommost layer is the first layer. - * As a result, on could use a generic pin, then overlay it with a specific icon. - * To make things even more practical, one can use all SVG's from the folder "assets/svg" and _substitute the color_ in it. - * E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;` - * - */ - icon?: string | TagRenderingConfigJson; - /** - * IconsOverlays are a list of extra icons/badges to overlay over the icon. - * The 'badge'-toggle changes their behaviour. - * If badge is set, it will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout. - * If badges is false, it'll be a simple overlay - * - * Note: strings are interpreted as icons, so layering and substituting is supported - */ - iconOverlays?: { if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson, badge?: boolean }[] - - /** - * A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ... - * Default is '40,40,center' - */ - iconSize?: string | TagRenderingConfigJson; - /** - * The rotation of an icon, useful for e.g. directions. - * Usage: 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)`` - */ - rotation?: string | TagRenderingConfigJson; - /** - * A HTML-fragment that is shown below the icon, for example: - *
{name}
- * - * If the icon is undefined, then the label is shown in the center of the feature. - * Note that, if the wayhandling hides the icon then no label is shown as well. - */ - label?: string | TagRenderingConfigJson; - - /** - * The color for way-elements and SVG-elements. - * If the value starts with "--", the style of the body element will be queried for the corresponding variable instead - */ - color?: string | TagRenderingConfigJson; - /** - * The stroke-width for way-elements - */ - width?: string | TagRenderingConfigJson; - - /** - * A dasharray, e.g. "5 6" - * The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap', - * Default value: "" (empty string == full line) - */ - dashArray?: string | TagRenderingConfigJson - - /** - * Wayhandling: should a way/area be displayed as: - * 0) The way itself - * 1) Only the centerpoint - * 2) The centerpoint and the way - */ - wayHandling?: number; + mapRendering: (PointRenderingConfigJson | LineRenderingConfigJson)[] /** * If set, this layer will pass all the features it receives onto the next layer. @@ -263,8 +203,19 @@ export interface LayerConfigJson { * * A 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. * + * At last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings. + * This is mainly create questions for a 'left' and a 'right' side of the road. + * These will be grouped and questions will be asked together */ - tagRenderings?: (string | {builtin: string, override: any} | TagRenderingConfigJson) [], + tagRenderings?: (string | {builtin: string, override: any} | TagRenderingConfigJson | { + rewrite: { + sourceString: string, + into: string[] + }[], + renderings: (string | {builtin: string, override: any} | TagRenderingConfigJson)[] + }) [], + + /** diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 2b355d545..de2abe0a1 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -118,18 +118,6 @@ export interface LayoutConfigJson { * Default: overpassMaxZoom + 1 */ osmApiTileSize?: number - - /** - * A tagrendering depicts how to show some tags or how to show a question for it. - * - * These tagrenderings are applied to _all_ the loaded layers and are a way to reuse tagrenderings. - * Note that if multiple themes are loaded (e.g. via the personal theme) - * that these roamingRenderings are applied to the layers of the OTHER themes too! - * - * In order to prevent them to do too much damage, all the overpass-tags of the layers are taken and combined as OR. - * These tag renderings will only show up if the object matches this filter. - */ - roamingRenderings?: (TagRenderingConfigJson | string)[], /** * An override applied on all layers of the theme. diff --git a/Models/ThemeConfig/Json/LineRenderingConfigJson.ts b/Models/ThemeConfig/Json/LineRenderingConfigJson.ts new file mode 100644 index 000000000..708366767 --- /dev/null +++ b/Models/ThemeConfig/Json/LineRenderingConfigJson.ts @@ -0,0 +1,38 @@ +import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; + +/** + * The LineRenderingConfig gives all details onto how to render a single line of a feature. + * + * This can be used if: + * + * - The feature is a line + * - The feature is an area + */ +export default interface LineRenderingConfigJson { + + /** + * The color for way-elements and SVG-elements. + * If the value starts with "--", the style of the body element will be queried for the corresponding variable instead + */ + color?: string | TagRenderingConfigJson; + /** + * The stroke-width for way-elements + */ + width?: string | TagRenderingConfigJson; + + /** + * A dasharray, e.g. "5 6" + * The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap', + * Default value: "" (empty string == full line) + */ + dashArray?: string | TagRenderingConfigJson + + /** + * The number of pixels this line should be moved. + * Use 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). + * + * IMPORTANT: 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') + * This simplifies programming. Refer to the CalculatedTags.md-documentation for more details + */ + offset?: number | TagRenderingConfigJson +} \ No newline at end of file diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts new file mode 100644 index 000000000..45f34b755 --- /dev/null +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -0,0 +1,60 @@ +import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; +import {AndOrTagConfigJson} from "./TagConfigJson"; + +/** + * The PointRenderingConfig gives all details onto how to render a single point of a feature. + * + * This can be used if: + * + * - The feature is a point + * - To render something at the centroid of an area, or at the start, end or projected centroid of a way + */ +export default interface PointRenderingConfigJson { + + /** + * All the locations that this point should be rendered at. + * Using `location: ["point", "centroid"] will always render centerpoint + */ + location: ("point" | "centroid" | "start" | "end")[] + + /** + * The icon for an element. + * Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets. + * + * The result of the icon is rendered as follows: + * the resulting string is interpreted as a _list_ of items, separated by ";". The bottommost layer is the first layer. + * As a result, on could use a generic pin, then overlay it with a specific icon. + * To make things even more practical, one can use all SVG's from the folder "assets/svg" and _substitute the color_ in it. + * E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;` + * + */ + icon?: string | TagRenderingConfigJson; + + /** + * A list of extra badges to show next to the icon as small badge + * They will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout. + * + * Note: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle + */ + iconBadges?: { if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson }[] + + + /** + * A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ... + * Default is '40,40,center' + */ + iconSize?: string | TagRenderingConfigJson; + /** + * The rotation of an icon, useful for e.g. directions. + * Usage: 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)`` + */ + rotation?: string | TagRenderingConfigJson; + /** + * A HTML-fragment that is shown below the icon, for example: + *
{name}
+ * + * If the icon is undefined, then the label is shown in the center of the feature. + * Note that, if the wayhandling hides the icon then no label is shown as well. + */ + label?: string | TagRenderingConfigJson; +} \ No newline at end of file diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 532690af7..0be6c6967 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -11,6 +11,12 @@ export interface TagRenderingConfigJson { * Used to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise */ id?: string, + + /** + * If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well. + * The first tagRendering of a group will always be a sticky element. + */ + group?: string /** * Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element. @@ -83,6 +89,7 @@ export interface TagRenderingConfigJson { * Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes */ mappings?: { + /** * If this condition is met, then the text under `then` will be shown. * If no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM. @@ -168,11 +175,11 @@ export interface TagRenderingConfigJson { */ ifnot?: AndOrTagConfigJson | string - }[] + /** + * If chosen as answer, these tags will be applied as well onto the object. + * Not compatible with multiAnswer + */ + addExtraTags?: string[] - /** - * If set to true, this tagRendering will escape the current layer and attach itself to all the other layers too. - * However, it will _only_ be shown if it matches the overpass-tags of the layer it was originally defined in. - */ - roaming?: boolean + }[] } \ No newline at end of file diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index ba6ca94b8..3ef796cd7 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -1,30 +1,24 @@ import {Translation} from "../../UI/i18n/Translation"; import SourceConfig from "./SourceConfig"; import TagRenderingConfig from "./TagRenderingConfig"; -import {TagsFilter} from "../../Logic/Tags/TagsFilter"; import PresetConfig from "./PresetConfig"; import {LayerConfigJson} from "./Json/LayerConfigJson"; import Translations from "../../UI/i18n/Translations"; import {TagUtils} from "../../Logic/Tags/TagUtils"; -import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; -import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; -import {Utils} from "../../Utils"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import BaseUIElement from "../../UI/BaseUIElement"; -import {FixedUiElement} from "../../UI/Base/FixedUiElement"; -import Combine from "../../UI/Base/Combine"; -import {VariableUiElement} from "../../UI/Base/VariableUIElement"; import FilterConfig from "./FilterConfig"; import {Unit} from "../Unit"; import DeleteConfig from "./DeleteConfig"; -import Svg from "../../Svg"; -import Img from "../../UI/Base/Img"; import MoveConfig from "./MoveConfig"; +import PointRenderingConfig from "./PointRenderingConfig"; +import WithContextLoader from "./WithContextLoader"; +import LineRenderingConfig from "./LineRenderingConfig"; +import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"; +import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"; +import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import BaseUIElement from "../../UI/BaseUIElement"; -export default class LayerConfig { - static WAYHANDLING_DEFAULT = 0; - static WAYHANDLING_CENTER_ONLY = 1; - static WAYHANDLING_CENTER_AND_WAY = 2; +export default class LayerConfig extends WithContextLoader { id: string; name: Translation; @@ -39,15 +33,10 @@ export default class LayerConfig { maxzoom: number; title?: TagRenderingConfig; titleIcons: TagRenderingConfig[]; - icon: TagRenderingConfig; - iconOverlays: { if: TagsFilter; then: TagRenderingConfig; badge: boolean }[]; - iconSize: TagRenderingConfig; - label: TagRenderingConfig; - rotation: TagRenderingConfig; - color: TagRenderingConfig; - width: TagRenderingConfig; - dashArray: TagRenderingConfig; - wayHandling: number; + + public readonly mapRendering: PointRenderingConfig[] + public readonly lineRendering: LineRenderingConfig[] + public readonly units: Unit[]; public readonly deletion: DeleteConfig | null; public readonly allowMove: MoveConfig | null @@ -67,10 +56,47 @@ export default class LayerConfig { context?: string, official: boolean = true ) { - context = context + "." + json.id; - const self = this; + super(json, context) this.id = json.id; + + if (json.source === undefined) { + throw "Layer " + this.id + " does not define a source section (" + context + ")" + } + + if (json.source.osmTags === undefined) { + throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers (" + context + ")" + + } + + this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 + + const osmTags = TagUtils.Tag( + json.source.osmTags, + context + "source.osmTags" + ); + + if (json.source["geoJsonSource"] !== undefined) { + throw context + "Use 'geoJson' instead of 'geoJsonSource'"; + } + + if (json.source["geojson"] !== undefined) { + throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)"; + } + + this.source = new SourceConfig( + { + osmTags: osmTags, + geojsonSource: json.source["geoJson"], + geojsonSourceLevel: json.source["geoJsonZoomLevel"], + overpassScript: json.source["overpassScript"], + isOsmCache: json.source["isOsmCache"], + mercatorCrs: json.source["mercatorCrs"] + }, + json.id + ); + + this.allowSplit = json.allowSplit ?? false; this.name = Translations.T(json.name, context + ".name"); this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`))) @@ -86,53 +112,7 @@ export default class LayerConfig { context + ".description" ); - let legacy = undefined; - if (json["overpassTags"] !== undefined) { - // @ts-ignore - legacy = TagUtils.Tag(json["overpassTags"], context + ".overpasstags"); - } - if (json.source !== undefined) { - this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 - if (legacy !== undefined) { - throw ( - context + - "Both the legacy 'layer.overpasstags' and the new 'layer.source'-field are defined" - ); - } - - let osmTags: TagsFilter = legacy; - if (json.source["osmTags"]) { - osmTags = TagUtils.Tag( - json.source["osmTags"], - context + "source.osmTags" - ); - } - - if (json.source["geoJsonSource"] !== undefined) { - throw context + "Use 'geoJson' instead of 'geoJsonSource'"; - } - - if (json.source["geojson"] !== undefined) { - throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)"; - } - - this.source = new SourceConfig( - { - osmTags: osmTags, - geojsonSource: json.source["geoJson"], - geojsonSourceLevel: json.source["geoJsonZoomLevel"], - overpassScript: json.source["overpassScript"], - isOsmCache: json.source["isOsmCache"], - }, - this.id - ); - } else { - this.source = new SourceConfig({ - osmTags: legacy, - }); - } - this.calculatedTags = undefined; if (json.calculatedTags !== undefined) { if (!official) { @@ -162,7 +142,6 @@ export default class LayerConfig { this.passAllFeatures = json.passAllFeatures ?? false; this.minzoom = json.minzoom ?? 0; this.minzoomVisible = json.minzoomVisible ?? this.minzoom; - this.wayHandling = json.wayHandling ?? 0; if (json.presets !== undefined && json.presets?.map === undefined) { throw "Presets should be a list of items (at " + context + ")" } @@ -208,103 +187,21 @@ export default class LayerConfig { return config; }); - /** Given a key, gets the corresponding property from the json (or the default if not found - * - * The found value is interpreted as a tagrendering and fetched/parsed - * */ - function tr(key: string, deflt) { - const v = json[key]; - if (v === undefined || v === null) { - if (deflt === undefined) { - return undefined; - } - return new TagRenderingConfig( - deflt, - self.source.osmTags, - `${context}.${key}.default value` - ); - } - if (typeof v === "string") { - const shared = SharedTagRenderings.SharedTagRendering.get(v); - if (shared) { - return shared; - } - } - return new TagRenderingConfig( - v, - self.source.osmTags, - `${context}.${key}` - ); + if (json.mapRendering === undefined) { + throw "MapRendering is undefined in " + context } - /** - * Converts a list of tagRenderingCOnfigJSON in to TagRenderingConfig - * A string is interpreted as a name to call - */ - function trs( - tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson)[], - readOnly = false - ) { - if (tagRenderings === undefined) { - return []; - } + this.mapRendering = json.mapRendering + .filter(r => r["icon"] !== undefined || r["label"] !== undefined) + .map((r, i) => new PointRenderingConfig(r, context + ".mapRendering[" + i + "]")) - return Utils.NoNull( - tagRenderings.map((renderingJson, i) => { - if (typeof renderingJson === "string") { - renderingJson = {builtin: renderingJson, override: undefined} - } + this.lineRendering = json.mapRendering + .filter(r => r["icon"] === undefined && r["label"] === undefined) + .map((r, i) => new LineRenderingConfig(r, context + ".mapRendering[" + i + "]")) - if (renderingJson["builtin"] !== undefined) { - const renderingId = renderingJson["builtin"] - if (renderingId === "questions") { - if (readOnly) { - throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify( - renderingJson - )}`; - } - return new TagRenderingConfig("questions", undefined, context); - } - - if (renderingJson["override"] !== undefined) { - const sharedJson = SharedTagRenderings.SharedTagRenderingJson.get(renderingId) - return new TagRenderingConfig( - Utils.Merge(renderingJson["override"], sharedJson), - self.source.osmTags, - `${context}.tagrendering[${i}]+override` - ); - } - - const shared = SharedTagRenderings.SharedTagRendering.get(renderingId); - - if (shared !== undefined) { - return shared; - } - if (Utils.runningFromConsole) { - return undefined; - } - - const keys = Array.from( - SharedTagRenderings.SharedTagRendering.keys() - ); - throw `Predefined tagRendering ${renderingId} not found in ${context}.\n Try one of ${keys.join( - ", " - )}\n If you intent to output this text literally, use {\"render\": } instead"}`; - } - - return new TagRenderingConfig( - renderingJson, - self.source.osmTags, - `${context}.tagrendering[${i}]` - ); - }) - ); - } - - this.tagRenderings = trs(json.tagRenderings, false); - - const missingIds = json.tagRenderings?.filter(tr => typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined) ?? []; + this.tagRenderings = this.ExtractLayerTagRenderings(json) + const missingIds = json.tagRenderings?.filter(tr => typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined && tr["rewrite"] === undefined) ?? []; if (missingIds.length > 0 && official) { console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) @@ -335,43 +232,10 @@ export default class LayerConfig { } } - this.titleIcons = trs(titleIcons, true); + this.titleIcons = this.ParseTagRenderings(titleIcons, true); - this.title = tr("title", undefined); - this.icon = tr("icon", ""); - this.iconOverlays = (json.iconOverlays ?? []).map((overlay, i) => { - let tr = new TagRenderingConfig( - overlay.then, - self.source.osmTags, - `iconoverlays.${i}` - ); - if ( - typeof overlay.then === "string" && - SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined - ) { - tr = SharedTagRenderings.SharedIcons.get(overlay.then); - } - return { - if: TagUtils.Tag(overlay.if), - then: tr, - badge: overlay.badge ?? false, - }; - }); - - const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; - if (iconPath.startsWith(Utils.assets_path)) { - const iconKey = iconPath.substr(Utils.assets_path.length); - if (Svg.All[iconKey] === undefined) { - throw "Builtin SVG asset not found: " + iconPath; - } - } - this.isShown = tr("isShown", "yes"); - this.iconSize = tr("iconSize", "40,40,center"); - this.label = tr("label", ""); - this.color = tr("color", "#0000ff"); - this.width = tr("width", "7"); - this.rotation = tr("rotation", "0"); - this.dashArray = tr("dashArray", ""); + this.title = this.tr("title", undefined); + this.isShown = this.tr("isShown", "yes"); this.deletion = null; if (json.deletion === true) { @@ -400,258 +264,128 @@ export default class LayerConfig { } } + public defaultIcon() : BaseUIElement | undefined{ + const mapRendering = this.mapRendering.filter(r => r.location.has("point"))[0] + if (mapRendering === undefined) { + return undefined + } + const defaultTags = new UIEventSource(TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"}))) + return mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html + } + + public ExtractLayerTagRenderings(json: LayerConfigJson): TagRenderingConfig[] { + + if (json.tagRenderings === undefined) { + return [] + } + + const normalTagRenderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] = [] + + + const renderingsToRewrite: ({ + rewrite: { + sourceString: string, + into: string[] + }, renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] + })[] = [] + for (let i = 0; i < json.tagRenderings.length; i++) { + const tr = json.tagRenderings[i]; + const rewriteDefined = tr["rewrite"] !== undefined + const renderingsDefined = tr["renderings"] + + if (!rewriteDefined && !renderingsDefined) { + // @ts-ignore + normalTagRenderings.push(tr) + continue + } + if (rewriteDefined && renderingsDefined) { + // @ts-ignore + renderingsToRewrite.push(tr) + continue + } + throw `Error in ${this._context}.tagrenderings[${i}]: got a value which defines either \`rewrite\` or \`renderings\`, but not both. Either define both or move the \`renderings\` out of this scope` + } + + const allRenderings = this.ParseTagRenderings(normalTagRenderings, false); + + if (renderingsToRewrite.length === 0) { + return allRenderings + } + + function prepConfig(keyToRewrite: string, target: string, tr: TagRenderingConfigJson) { + + function replaceRecursive(transl: string | any) { + if (typeof transl === "string") { + return transl.replace(keyToRewrite, target) + } + if (transl.map !== undefined) { + return transl.map(o => replaceRecursive(o)) + } + transl = {...transl} + for (const key in transl) { + transl[key] = replaceRecursive(transl[key]) + } + return transl + } + + const orig = tr; + tr = replaceRecursive(tr) + + tr.id = target + "-" + orig.id + tr.group = target + return tr + } + + const rewriteGroups: Map = new Map() + for (const rewriteGroup of renderingsToRewrite) { + + const tagRenderings = rewriteGroup.renderings + const textToReplace = rewriteGroup.rewrite.sourceString + const targets = rewriteGroup.rewrite.into + for (const target of targets) { + const parsedRenderings = this.ParseTagRenderings(tagRenderings, false, tr => prepConfig(textToReplace, target, tr)) + + if (!rewriteGroups.has(target)) { + rewriteGroups.set(target, []) + } + rewriteGroups.get(target).push(...parsedRenderings) + } + } + + + rewriteGroups.forEach((group, groupName) => { + group.push(new TagRenderingConfig({ + id: "questions", + group: groupName + })) + }) + + rewriteGroups.forEach(group => { + allRenderings.push(...group) + }) + + + return allRenderings; + + } + public CustomCodeSnippets(): string[] { if (this.calculatedTags === undefined) { return []; } - return this.calculatedTags.map((code) => code[1]); } - public AddRoamingRenderings(addAll: { - tagRenderings: TagRenderingConfig[]; - titleIcons: TagRenderingConfig[]; - iconOverlays: { - if: TagsFilter; - then: TagRenderingConfig; - badge: boolean; - }[]; - }): LayerConfig { - let insertionPoint = this.tagRenderings - .map((tr) => tr.IsQuestionBoxElement()) - .indexOf(true); - if (insertionPoint < 0) { - // No 'questions' defined - we just add them all to the end - insertionPoint = this.tagRenderings.length; - } - this.tagRenderings.splice(insertionPoint, 0, ...addAll.tagRenderings); - - this.iconOverlays.push(...addAll.iconOverlays); - for (const icon of addAll.titleIcons) { - this.titleIcons.splice(0, 0, icon); - } - return this; - } - - public GetRoamingRenderings(): { - tagRenderings: TagRenderingConfig[]; - titleIcons: TagRenderingConfig[]; - iconOverlays: { - if: TagsFilter; - then: TagRenderingConfig; - badge: boolean; - }[]; - } { - const tagRenderings = this.tagRenderings.filter((tr) => tr.roaming); - const titleIcons = this.titleIcons.filter((tr) => tr.roaming); - const iconOverlays = this.iconOverlays.filter((io) => io.then.roaming); - - return { - tagRenderings: tagRenderings, - titleIcons: titleIcons, - iconOverlays: iconOverlays, - }; - } - - public GenerateLeafletStyle( - tags: UIEventSource, - clickable: boolean - ): { - icon: { - html: BaseUIElement; - iconSize: [number, number]; - iconAnchor: [number, number]; - popupAnchor: [number, number]; - iconUrl: string; - className: string; - }; - color: string; - weight: number; - dashArray: number[]; - } { - function num(str, deflt = 40) { - const n = Number(str); - if (isNaN(n)) { - return deflt; - } - return n; - } - - function rendernum(tr: TagRenderingConfig, deflt: number) { - const str = Number(render(tr, "" + deflt)); - const n = Number(str); - if (isNaN(n)) { - return deflt; - } - return n; - } - - function render(tr: TagRenderingConfig, deflt?: string) { - if (tags === undefined) { - return deflt - } - const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; - return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); - } - - const iconSize = render(this.iconSize, "40,40,center").split(","); - const dashArray = render(this.dashArray)?.split(" ")?.map(Number); - let color = render(this.color, "#00f"); - - if (color.startsWith("--")) { - color = getComputedStyle(document.body).getPropertyValue( - "--catch-detail-color" - ); - } - - const weight = rendernum(this.width, 5); - - const iconW = num(iconSize[0]); - let iconH = num(iconSize[1]); - const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center"; - - let anchorW = iconW / 2; - let anchorH = iconH / 2; - if (mode === "left") { - anchorW = 0; - } - if (mode === "right") { - anchorW = iconW; - } - - if (mode === "top") { - anchorH = 0; - } - if (mode === "bottom") { - anchorH = iconH; - } - - const iconUrlStatic = render(this.icon); - const self = this; - - function genHtmlFromString(sourcePart: string, rotation: string): BaseUIElement { - const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; - let html: BaseUIElement = new FixedUiElement( - `` - ); - const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/); - if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { - html = new Img( - (Svg.All[match[1] + ".svg"] as string).replace( - /#000000/g, - match[2] - ), - true - ).SetStyle(style); - } - return html; - } - - - const mappedHtml = tags?.map((tgs) => { - // What do you mean, 'tgs' is never read? - // It is read implicitly in the 'render' method - const iconUrl = render(self.icon); - const rotation = render(self.rotation, "0deg"); - - let htmlParts: BaseUIElement[] = []; - let sourceParts = Utils.NoNull( - iconUrl.split(";").filter((prt) => prt != "") - ); - for (const sourcePart of sourceParts) { - htmlParts.push(genHtmlFromString(sourcePart, rotation)); - } - - let badges = []; - for (const iconOverlay of self.iconOverlays) { - if (!iconOverlay.if.matchesProperties(tgs)) { - continue; - } - if (iconOverlay.badge) { - const badgeParts: BaseUIElement[] = []; - const renderValue = iconOverlay - .then - .GetRenderValue(tgs) - - if (renderValue === undefined) { - continue; - } - - const partDefs = renderValue.txt.split(";") - .filter((prt) => prt != ""); - - for (const badgePartStr of partDefs) { - badgeParts.push(genHtmlFromString(badgePartStr, "0")); - } - - const badgeCompound = new Combine(badgeParts).SetStyle( - "display:flex;position:relative;width:100%;height:100%;" - ); - - badges.push(badgeCompound); - } else { - htmlParts.push( - genHtmlFromString(iconOverlay.then.GetRenderValue(tgs).txt, "0") - ); - } - } - - if (badges.length > 0) { - const badgesComponent = new Combine(badges).SetStyle( - "display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;" - ); - htmlParts.push(badgesComponent); - } - - if (sourceParts.length == 0) { - iconH = 0; - } - try { - const label = self.label - ?.GetRenderValue(tgs) - ?.Subs(tgs) - ?.SetClass("block text-center") - ?.SetStyle("margin-top: " + (iconH + 2) + "px"); - if (label !== undefined) { - htmlParts.push( - new Combine([label]).SetClass("flex flex-col items-center") - ); - } - } catch (e) { - console.error(e, tgs); - } - return new Combine(htmlParts); - }); - - return { - icon: { - html: mappedHtml === undefined ? new FixedUiElement(self.icon.render.txt) : new VariableUiElement(mappedHtml), - iconSize: [iconW, iconH], - iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH], - iconUrl: iconUrlStatic, - className: clickable - ? "leaflet-div-icon" - : "leaflet-div-icon unclickable", - }, - color: color, - weight: weight, - dashArray: dashArray, - }; - } - public ExtractImages(): Set { const parts: Set[] = []; parts.push(...this.tagRenderings?.map((tr) => tr.ExtractImages(false))); parts.push(...this.titleIcons?.map((tr) => tr.ExtractImages(true))); - parts.push(this.icon?.ExtractImages(true)); - parts.push( - ...this.iconOverlays?.map((overlay) => overlay.then.ExtractImages(true)) - ); for (const preset of this.presets) { parts.push(new Set(preset.description?.ExtractImages(false))); } - + for (const pointRenderingConfig of this.mapRendering) { + parts.push(pointRenderingConfig.ExtractImages()) + } const allIcons = new Set(); for (const part of parts) { part?.forEach(allIcons.add, allIcons); @@ -659,4 +393,8 @@ export default class LayerConfig { return allIcons; } + + public isLeftRightSensitive(): boolean { + return this.lineRendering.some(lr => lr.leftRightSensitive) + } } \ No newline at end of file diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 1d89a104d..8702a3cbf 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -1,7 +1,5 @@ import {Translation} from "../../UI/i18n/Translation"; -import TagRenderingConfig from "./TagRenderingConfig"; import {LayoutConfigJson} from "./Json/LayoutConfigJson"; -import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; import AllKnownLayers from "../../Customizations/AllKnownLayers"; import {Utils} from "../../Utils"; import LayerConfig from "./LayerConfig"; @@ -25,7 +23,6 @@ export default class LayoutConfig { public readonly startLat: number; public readonly startLon: number; public readonly widenFactor: number; - public readonly roamingRenderings: TagRenderingConfig[]; public readonly defaultBackgroundId?: string; public layers: LayerConfig[]; public tileLayerSources: TilesourceConfig[] @@ -55,6 +52,7 @@ export default class LayoutConfig { public readonly overpassMaxZoom: number public readonly osmApiTileSize: number public readonly official: boolean; + public readonly trackAllNodes : boolean; constructor(json: LayoutConfigJson, official = true, context?: string) { this.official = official; @@ -64,6 +62,8 @@ export default class LayoutConfig { this.credits = json.credits; this.version = json.version; this.language = []; + this.trackAllNodes = false + if (typeof json.language === "string") { this.language = [json.language]; } else { @@ -93,45 +93,16 @@ export default class LayoutConfig { if(json.widenFactor > 20){ throw "Widenfactor is very big, use a value between 1 and 5 (current value is "+json.widenFactor+") at "+context } + this.widenFactor = json.widenFactor ?? 1.5; - this.roamingRenderings = (json.roamingRenderings ?? []).map((tr, i) => { - if (typeof tr === "string") { - if (SharedTagRenderings.SharedTagRendering.get(tr) !== undefined) { - return SharedTagRenderings.SharedTagRendering.get(tr); - } - } - return new TagRenderingConfig(tr, undefined, `${this.id}.roaming_renderings[${i}]`); - } - ); + this.defaultBackgroundId = json.defaultBackgroundId; this.tileLayerSources = (json.tileLayerSources??[]).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`)) - this.layers = LayoutConfig.ExtractLayers(json, official, context); - - // ALl the layers are constructed, let them share tagRenderings now! - const roaming: { r, source: LayerConfig }[] = [] - for (const layer of this.layers) { - roaming.push({r: layer.GetRoamingRenderings(), source: layer}); - } - - for (const layer of this.layers) { - for (const r of roaming) { - if (r.source == layer) { - continue; - } - layer.AddRoamingRenderings(r.r); - } - } - - for (const layer of this.layers) { - layer.AddRoamingRenderings( - { - titleIcons: [], - iconOverlays: [], - tagRenderings: this.roamingRenderings - } - ); - } - + const layerInfo = LayoutConfig.ExtractLayers(json, official, context); + this.layers = layerInfo.layers + this.trackAllNodes = layerInfo.extractAllNodes + + this.clustering = { maxZoom: 16, minNeededElements: 25, @@ -181,10 +152,11 @@ export default class LayoutConfig { } - private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): LayerConfig[] { + private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): {layers: LayerConfig[], extractAllNodes: boolean} { const result: LayerConfig[] = [] - + let exportAllNodes = false json.layers.forEach((layer, i) => { + if (typeof layer === "string") { if (AllKnownLayers.sharedLayersJson.get(layer) !== undefined) { if (json.overrideAll !== undefined) { @@ -211,12 +183,19 @@ export default class LayoutConfig { result.push(newLayer) return } + // @ts-ignore let names = layer.builtin; if (typeof names === "string") { names = [names] } names.forEach(name => { + + if(name === "type_node"){ + // This is a very special layer which triggers special behaviour + exportAllNodes = true; + } + const shared = AllKnownLayers.sharedLayersJson.get(name); if (shared === undefined) { throw `Unknown shared/builtin layer ${name} at ${context}.layers[${i}]. Available layers are ${Array.from(AllKnownLayers.sharedLayersJson.keys()).join(", ")}`; @@ -233,7 +212,7 @@ export default class LayoutConfig { }); - return result + return {layers: result, extractAllNodes: exportAllNodes} } public CustomCodeSnippets(): string[] { @@ -304,10 +283,13 @@ export default class LayoutConfig { } rewriting.forEach((value, key) => { console.log("Rewriting", key, "==>", value) - originalJson = originalJson.replace(new RegExp(key, "g"), value) }) return new LayoutConfig(JSON.parse(originalJson), false, "Layout rewriting") } + + public isLeftRightSensitive(){ + return this.layers.some(l => l.isLeftRightSensitive()) + } } \ No newline at end of file diff --git a/Models/ThemeConfig/LegacyJsonConvert.ts b/Models/ThemeConfig/LegacyJsonConvert.ts new file mode 100644 index 000000000..43274643c --- /dev/null +++ b/Models/ThemeConfig/LegacyJsonConvert.ts @@ -0,0 +1,108 @@ +import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"; + +export default class LegacyJsonConvert { + + /** + * Updates the config file in-place + * @param config + * @private + */ + public static fixLayerConfig(config: any): void { + if (config["overpassTags"]) { + config.source = config.source ?? {} + config.source.osmTags = config["overpassTags"] + delete config["overpassTags"] + } + + if (config.tagRenderings !== undefined) { + for (const tagRendering of config.tagRenderings) { + if (tagRendering["#"] !== undefined) { + tagRendering["id"] = tagRendering["#"] + delete tagRendering["#"] + } + if (tagRendering["id"] === undefined) { + if (tagRendering["freeform"]?.key !== undefined) { + tagRendering["id"] = config.id + "-" + tagRendering["freeform"]["key"] + } + } + } + } + + if (config.mapRendering === undefined && config.id !== "sidewalks") { + // This is a legacy format, lets create a pointRendering + let location: ("point" | "centroid")[] = ["point"] + let wayHandling: number = config["wayHandling"] ?? 0 + if (wayHandling === 2) { + location = ["point", "centroid"] + } + config.mapRendering = [ + { + icon: config["icon"], + iconBadges: config["iconOverlays"], + label: config["label"], + iconSize: config["iconSize"], + location, + rotation: config["rotation"] + } + ] + + if (wayHandling !== 1) { + const lineRenderConfig = { + color: config["color"], + width: config["width"], + dashArray: config["dashArray"] + } + if (Object.keys(lineRenderConfig).length > 0) { + config.mapRendering.push(lineRenderConfig) + } + } + + + delete config["color"] + delete config["width"] + delete config["dashArray"] + + delete config["icon"] + delete config["iconOverlays"] + delete config["label"] + delete config["iconSize"] + delete config["rotation"] + delete config["wayHandling"] + + } + + for (const mapRenderingElement of config.mapRendering) { + if (mapRenderingElement["iconOverlays"] !== undefined) { + mapRenderingElement["iconBadges"] = mapRenderingElement["iconOverlays"] + } + for (const overlay of mapRenderingElement["iconBadges"] ?? []) { + if (overlay["badge"] !== true) { + console.log("Warning: non-overlay element for ", config.id) + } + delete overlay["badge"] + } + } + + } + + + /** + * Given an old (parsed) JSON-config, will (in place) fix some issues + * @param oldThemeConfig: the config to update to the latest format + */ + public static fixThemeConfig(oldThemeConfig: any): void { + for (const layerConfig of oldThemeConfig.layers ?? []) { + if (typeof layerConfig === "string" || layerConfig["builtin"] !== undefined) { + continue + } + // @ts-ignore + LegacyJsonConvert.fixLayerConfig(layerConfig) + } + + if (oldThemeConfig["roamingRenderings"] !== undefined && oldThemeConfig["roamingRenderings"].length == 0) { + delete oldThemeConfig["roamingRenderings"] + } + } + + +} \ No newline at end of file diff --git a/Models/ThemeConfig/LineRenderingConfig.ts b/Models/ThemeConfig/LineRenderingConfig.ts new file mode 100644 index 000000000..1f0397888 --- /dev/null +++ b/Models/ThemeConfig/LineRenderingConfig.ts @@ -0,0 +1,70 @@ +import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"; +import WithContextLoader from "./WithContextLoader"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import TagRenderingConfig from "./TagRenderingConfig"; +import {Utils} from "../../Utils"; +import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"; + +export default class LineRenderingConfig extends WithContextLoader { + + + public readonly color: TagRenderingConfig; + public readonly width: TagRenderingConfig; + public readonly dashArray: TagRenderingConfig; + public readonly offset: TagRenderingConfig; + public readonly leftRightSensitive: boolean + + constructor(json: LineRenderingConfigJson, context: string) { + super(json, context) + this.color = this.tr("color", "#0000ff"); + this.width = this.tr("width", "7"); + this.dashArray = this.tr("dashArray", ""); + + this.leftRightSensitive = json.offset !== undefined && json.offset !== 0 && json.offset !== "0" + + this.offset = this.tr("offset", "0"); + } + + public GenerateLeafletStyle(tags: {}): + { + color: string, + weight: number, + dashArray: string, + offset: number + } { + function rendernum(tr: TagRenderingConfig, deflt: number) { + const str = Number(render(tr, "" + deflt)); + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + function render(tr: TagRenderingConfig, deflt?: string) { + if (tags === undefined) { + return deflt + } + const str = tr?.GetRenderValue(tags)?.txt ?? deflt; + return Utils.SubstituteKeys(str, tags)?.replace(/{.*}/g, ""); + } + + const dashArray = render(this.dashArray); + let color = render(this.color, "#00f"); + if (color.startsWith("--")) { + color = getComputedStyle(document.body).getPropertyValue( + "--catch-detail-color" + ); + } + + const weight = rendernum(this.width, 5); + const offset = rendernum(this.offset, 0) + return { + color, + weight, + dashArray, + offset + } + } + +} \ No newline at end of file diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts new file mode 100644 index 000000000..666f9d047 --- /dev/null +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -0,0 +1,263 @@ +import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"; +import TagRenderingConfig from "./TagRenderingConfig"; +import {TagsFilter} from "../../Logic/Tags/TagsFilter"; +import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; +import {TagUtils} from "../../Logic/Tags/TagUtils"; +import {Utils} from "../../Utils"; +import Svg from "../../Svg"; +import WithContextLoader from "./WithContextLoader"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import BaseUIElement from "../../UI/BaseUIElement"; +import {FixedUiElement} from "../../UI/Base/FixedUiElement"; +import Img from "../../UI/Base/Img"; +import Combine from "../../UI/Base/Combine"; +import {VariableUiElement} from "../../UI/Base/VariableUIElement"; + +export default class PointRenderingConfig extends WithContextLoader { + + private static readonly allowed_location_codes = new Set(["point", "centroid","start","end"]) + public readonly location: Set<"point" | "centroid" | "start" | "end"> + + public readonly icon: TagRenderingConfig; + public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]; + public readonly iconSize: TagRenderingConfig; + public readonly label: TagRenderingConfig; + public readonly rotation: TagRenderingConfig; + + constructor(json: PointRenderingConfigJson, context: string) { + super(json, context) + + if(typeof json.location === "string"){ + json.location = [json.location] + } + + this.location = new Set(json.location) + + this.location.forEach(l => { + const allowed = PointRenderingConfig.allowed_location_codes + if(!allowed.has(l)){ + throw `A point rendering has an invalid location: '${l}' is not one of ${Array.from(allowed).join(", ")} (at ${context}.location)` + } + }) + + if(this.location.size == 0){ + throw "A pointRendering should have at least one 'location' to defined where it should be rendered. (At "+context+".location)" + } + this.icon = this.tr("icon", ""); + this.iconBadges = (json.iconBadges ?? []).map((overlay, i) => { + let tr : TagRenderingConfig; + if (typeof overlay.then === "string" && + SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined) { + tr = SharedTagRenderings.SharedIcons.get(overlay.then); + }else{ + tr = new TagRenderingConfig( + overlay.then, + `iconBadges.${i}` + ); + } + return { + if: TagUtils.Tag(overlay.if), + then: tr + }; + }); + + const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; + if (iconPath.startsWith(Utils.assets_path)) { + const iconKey = iconPath.substr(Utils.assets_path.length); + if (Svg.All[iconKey] === undefined) { + throw "Builtin SVG asset not found: " + iconPath; + } + } + this.iconSize = this.tr("iconSize", "40,40,center"); + this.label = this.tr("label", undefined); + this.rotation = this.tr("rotation", "0"); + } + + + public ExtractImages(): Set { + const parts: Set[] = []; + parts.push(this.icon?.ExtractImages(true)); + parts.push( + ...this.iconBadges?.map((overlay) => overlay.then.ExtractImages(true)) + ); + + const allIcons = new Set(); + for (const part of parts) { + part?.forEach(allIcons.add, allIcons); + } + return allIcons; + } + + /** + * Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that + * The element will fill 100% and be positioned absolutely with top:0 and left: 0 + */ + private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement { + if (htmlSpec === undefined) { + return undefined; + } + const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/); + if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { + const svg = (Svg.All[match[1] + ".svg"] as string) + const targetColor = match[2] + const img = new Img(svg.replace(/#000000/g, targetColor), true) + .SetStyle(style) + if(isBadge){ + img.SetClass("badge") + } + return img + } else { + return new FixedUiElement(``); + } + } + + private static FromHtmlMulti(multiSpec: string, rotation: string , isBadge: boolean, defaultElement: BaseUIElement = undefined){ + if(multiSpec === undefined){ + return defaultElement + } + const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; + + const htmlDefs = multiSpec.trim()?.split(";") ?? [] + const elements = Utils.NoEmpty(htmlDefs).map(def => PointRenderingConfig.FromHtmlSpec(def, style, isBadge)) + if (elements.length === 0) { + return defaultElement + } else { + return new Combine(elements).SetClass("relative block w-full h-full") + } + } + + public GetSimpleIcon(tags: UIEventSource): BaseUIElement { + const self = this; + if (this.icon === undefined) { + return undefined; + } + return new VariableUiElement(tags.map(tags => { + const rotation = self.rotation?.GetRenderValue(tags)?.txt ?? "0deg" + + const htmlDefs = Utils.SubstituteKeys(self.icon.GetRenderValue(tags)?.txt, tags) + let defaultPin : BaseUIElement = undefined + if(self.label === undefined){ + defaultPin = Svg.teardrop_with_hole_green_svg() + } + return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation,false, defaultPin) + })).SetClass("w-full h-full block") + } + + private GetBadges(tags: UIEventSource): BaseUIElement { + if (this.iconBadges.length === 0) { + return undefined + } + return new VariableUiElement( + tags.map(tags => { + + const badgeElements = this.iconBadges.map(badge => { + + if (!badge.if.matchesProperties(tags)) { + // Doesn't match... + return undefined + } + + const htmlDefs = Utils.SubstituteKeys(badge.then.GetRenderValue(tags)?.txt, tags) + const badgeElement= PointRenderingConfig.FromHtmlMulti(htmlDefs, "0", true)?.SetClass("block relative") + if(badgeElement === undefined){ + return undefined; + } + return new Combine([badgeElement]).SetStyle("width: 1.5rem").SetClass("block") + + }) + + return new Combine(badgeElements).SetClass("inline-flex h-full") + })).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0") + } + + private GetLabel(tags: UIEventSource): BaseUIElement { + if (this.label === undefined) { + return undefined; + } + const self = this; + return new VariableUiElement(tags.map(tags => { + const label = self.label + ?.GetRenderValue(tags) + ?.Subs(tags) + ?.SetClass("block text-center") + return new Combine([label]).SetClass("flex flex-col items-center mt-1") + })) + + } + + public GenerateLeafletStyle( + tags: UIEventSource, + clickable: boolean, + options?: { + noSize: false | boolean + } + ): + { + html: BaseUIElement; + iconSize: [number, number]; + iconAnchor: [number, number]; + popupAnchor: [number, number]; + iconUrl: string; + className: string; + } { + function num(str, deflt = 40) { + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + function render(tr: TagRenderingConfig, deflt?: string) { + if (tags === undefined) { + return deflt + } + const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; + return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); + } + + const iconSize = render(this.iconSize, "40,40,center").split(","); + + const iconW = num(iconSize[0]); + let iconH = num(iconSize[1]); + const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center"; + + let anchorW = iconW / 2; + let anchorH = iconH / 2; + if (mode === "left") { + anchorW = 0; + } + if (mode === "right") { + anchorW = iconW; + } + + if (mode === "top") { + anchorH = 0; + } + if (mode === "bottom") { + anchorH = iconH; + } + + + const iconAndBadges = new Combine([this.GetSimpleIcon(tags), this.GetBadges(tags)]) + .SetClass("block relative") + + if(!options?.noSize){ + iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`) + }else{ + iconAndBadges.SetClass("w-full h-full") + } + + return { + html: new Combine([iconAndBadges, this.GetLabel(tags)]).SetStyle("flex flex-col"), + iconSize: [iconW, iconH], + iconAnchor: [anchorW, anchorH], + popupAnchor: [0, 3 - anchorH], + iconUrl: undefined, + className: clickable + ? "leaflet-div-icon" + : "leaflet-div-icon unclickable", + }; + } + +} \ No newline at end of file diff --git a/Models/ThemeConfig/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts index 1d223bd2b..b378d0acd 100644 --- a/Models/ThemeConfig/SourceConfig.ts +++ b/Models/ThemeConfig/SourceConfig.ts @@ -1,4 +1,5 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter"; +import {RegexTag} from "../../Logic/Tags/RegexTag"; export default class SourceConfig { @@ -7,8 +8,10 @@ export default class SourceConfig { public readonly geojsonSource?: string; public readonly geojsonZoomLevel?: number; public readonly isOsmCacheLayer: boolean; + public readonly mercatorCrs: boolean; constructor(params: { + mercatorCrs?: boolean; osmTags?: TagsFilter, overpassScript?: string, geojsonSource?: string, @@ -33,10 +36,15 @@ export default class SourceConfig { console.error(params) throw `Source said it is a OSM-cached layer, but didn't define the actual source of the cache (in context ${context})` } - this.osmTags = params.osmTags; + if(params.geojsonSource !== undefined && params.geojsonSourceLevel !== undefined){ + if(! ["x","y","x_min","x_max","y_min","Y_max"].some(toSearch => params.geojsonSource.indexOf(toSearch) > 0)){ + throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})` + }} + this.osmTags = params.osmTags ?? new RegexTag("id",/.*/); this.overpassScript = params.overpassScript; this.geojsonSource = params.geojsonSource; this.geojsonZoomLevel = params.geojsonSourceLevel; this.isOsmCacheLayer = params.isOsmCache ?? false; + this.mercatorCrs = params.mercatorCrs ?? false; } } \ No newline at end of file diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 40b7bdfe1..7ef2c5fec 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -6,6 +6,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils"; import {And} from "../../Logic/Tags/And"; import ValidatedTextField from "../../UI/Input/ValidatedTextField"; import {Utils} from "../../Utils"; +import {Tag} from "../../Logic/Tags/Tag"; /*** * The parsed version of TagRenderingConfigJSON @@ -14,6 +15,7 @@ import {Utils} from "../../Utils"; export default class TagRenderingConfig { readonly id: string; + readonly group: string; readonly render?: Translation; readonly question?: Translation; readonly condition?: TagsFilter; @@ -36,10 +38,10 @@ export default class TagRenderingConfig { readonly ifnot?: TagsFilter, readonly then: Translation readonly hideInAnswer: boolean | TagsFilter + readonly addExtraTags: Tag[] }[] - readonly roaming: boolean; - constructor(json: string | TagRenderingConfigJson, conditionIfRoaming: TagsFilter, context?: string) { + constructor(json: string | TagRenderingConfigJson, context?: string) { if (json === "questions") { // Very special value @@ -47,7 +49,14 @@ export default class TagRenderingConfig { this.question = null; this.condition = null; } - + + + if(typeof json === "number"){ + this.render = Translations.WT( ""+json) + return; + } + + if (json === undefined) { throw "Initing a TagRenderingConfig with undefined in " + context; } @@ -59,18 +68,10 @@ export default class TagRenderingConfig { this.id = json.id ?? ""; + this.group = json.group ?? ""; this.render = Translations.T(json.render, context + ".render"); this.question = Translations.T(json.question, context + ".question"); - this.roaming = json.roaming ?? false; - if(this.roaming){ - console.warn("Deprecation notice: roaming renderings will be scrapped.", this.id, context) - } - const condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`); - if (this.roaming && conditionIfRoaming !== undefined) { - this.condition = new And([condition, conditionIfRoaming]); - } else { - this.condition = condition; - } + this.condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`); if (json.freeform) { if(json.freeform.addExtraTags !== undefined && json.freeform.addExtraTags.map === undefined){ @@ -119,21 +120,24 @@ export default class TagRenderingConfig { this.mappings = json.mappings.map((mapping, i) => { - + const ctx = `${context}.mapping[${i}]` if (mapping.then === undefined) { - throw `${context}.mapping[${i}]: Invalid mapping: if without body` + throw `${ctx}: Invalid mapping: if without body` } if (mapping.ifnot !== undefined && !this.multiAnswer) { - throw `${context}.mapping[${i}]: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer` + throw `${ctx}: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer` } if (mapping.if === undefined) { - throw `${context}.mapping[${i}]: Invalid mapping: "if" is not defined, but the tagrendering is not a multianswer` + throw `${ctx}: Invalid mapping: "if" is not defined, but the tagrendering is not a multianswer` } if (typeof mapping.if !== "string" && mapping.if["length"] !== undefined) { - throw `${context}.mapping[${i}]: Invalid mapping: "if" is defined as an array. Use {"and": } or {"or": } instead` + throw `${ctx}: Invalid mapping: "if" is defined as an array. Use {"and": } or {"or": } instead` + } + + if(mapping.addExtraTags !== undefined && this.multiAnswer){ + throw `${ctx}: Invalid mapping: got a multi-Answer with addExtraTags; this is not allowed` } - let hideInAnswer: boolean | TagsFilter = false; if (typeof mapping.hideInAnswer === "boolean") { @@ -141,12 +145,12 @@ export default class TagRenderingConfig { } else if (mapping.hideInAnswer !== undefined) { hideInAnswer = TagUtils.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`); } - const mappingContext = `${context}.mapping[${i}]` const mp = { - if: TagUtils.Tag(mapping.if, `${mappingContext}.if`), - ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${mappingContext}.ifnot`) : undefined), - then: Translations.T(mapping.then, `{mappingContext}.then`), - hideInAnswer: hideInAnswer + if: TagUtils.Tag(mapping.if, `${ctx}.if`), + ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined), + then: Translations.T(mapping.then, `${ctx}.then`), + hideInAnswer: hideInAnswer, + addExtraTags: (mapping.addExtraTags??[]).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`)) }; if (this.question) { if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) { @@ -224,7 +228,6 @@ export default class TagRenderingConfig { } } - /** * Returns true if it is known or not shown, false if the question should be asked * @constructor @@ -257,11 +260,6 @@ export default class TagRenderingConfig { return false; } - - public IsQuestionBoxElement(): boolean { - return this.question === null && this.condition === null; - } - /** * Gets all the render values. Will return multiple render values if 'multianswer' is enabled. * The result will equal [GetRenderValue] if not 'multiAnswer' @@ -306,7 +304,7 @@ export default class TagRenderingConfig { * Not compatible with multiAnswer - use GetRenderValueS instead in that case * @constructor */ - public GetRenderValue(tags: any): Translation { + public GetRenderValue(tags: any, defltValue: any = undefined): Translation { if (this.mappings !== undefined && !this.multiAnswer) { for (const mapping of this.mappings) { if (mapping.if === undefined) { @@ -326,7 +324,7 @@ export default class TagRenderingConfig { if (tags[this.freeform.key] !== undefined) { return this.render; } - return undefined; + return defltValue; } public ExtractImages(isIcon: boolean): Set { diff --git a/Models/ThemeConfig/WithContextLoader.ts b/Models/ThemeConfig/WithContextLoader.ts new file mode 100644 index 000000000..fea75511e --- /dev/null +++ b/Models/ThemeConfig/WithContextLoader.ts @@ -0,0 +1,102 @@ +import TagRenderingConfig from "./TagRenderingConfig"; +import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; +import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; +import {Utils} from "../../Utils"; + +export default class WithContextLoader { + private readonly _json: any; + protected readonly _context: string; + + constructor(json: any, context: string) { + this._json = json; + this._context = context; + } + + /** Given a key, gets the corresponding property from the json (or the default if not found + * + * The found value is interpreted as a tagrendering and fetched/parsed + * */ + public tr(key: string, deflt) { + const v = this._json[key]; + if (v === undefined || v === null) { + if (deflt === undefined) { + return undefined; + } + return new TagRenderingConfig( + deflt, + `${this._context}.${key}.default value` + ); + } + if (typeof v === "string") { + const shared = SharedTagRenderings.SharedTagRendering.get(v); + if (shared) { + return shared; + } + } + return new TagRenderingConfig( + v, + `${this._context}.${key}` + ); + } + + /** + * Converts a list of tagRenderingCOnfigJSON in to TagRenderingConfig + * A string is interpreted as a name to call + */ + public ParseTagRenderings( + tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson)[], + readOnly = false, + prepConfig: ((config: TagRenderingConfigJson) => TagRenderingConfigJson) = undefined + ) : TagRenderingConfig[]{ + if (tagRenderings === undefined) { + return []; + } + + const context = this._context + const renderings: TagRenderingConfig[] = [] + if (prepConfig === undefined) { + prepConfig = c => c + } + for (let i = 0; i < tagRenderings.length; i++) { + let renderingJson = tagRenderings[i] + if (typeof renderingJson === "string") { + renderingJson = {builtin: renderingJson, override: undefined} + } + + if (renderingJson["builtin"] !== undefined) { + const renderingId = renderingJson["builtin"] + if (renderingId === "questions") { + if (readOnly) { + throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify( + renderingJson + )}`; + } + + const tr = new TagRenderingConfig("questions", context); + renderings.push(tr) + continue; + } + + let sharedJson = SharedTagRenderings.SharedTagRenderingJson.get(renderingId) + if (sharedJson === undefined) { + const keys = Array.from(SharedTagRenderings.SharedTagRenderingJson.keys()); + throw `Predefined tagRendering ${renderingId} not found in ${context}.\n Try one of ${keys.join( + ", " + )}\n If you intent to output this text literally, use {\"render\": } instead"}`; + } + if (renderingJson["override"] !== undefined) { + sharedJson = Utils.Merge(renderingJson["override"], sharedJson) + } + renderingJson = sharedJson + } + + + const patchedConfig = prepConfig(renderingJson) + + const tr = new TagRenderingConfig(patchedConfig, `${context}.tagrendering[${i}]`); + renderings.push(tr) + } + + return renderings; + } +} \ No newline at end of file diff --git a/README.md b/README.md index 566ab402a..6f05efe8f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ It is possible to quickly make and distribute your own theme ## Examples +- [An overview of all official themes](https://pietervdvn.github.io/mc/develop/index.html). - [Buurtnatuur.be](http://buurtnatuur.be), developed for the Belgian [Green party](https://www.groen.be/). They also funded the initial development! - [Cyclofix](https://pietervdvn.github.io/MapComplete/index.html?layout=cyclofix), further development @@ -43,7 +44,7 @@ It is possible to quickly make and distribute your own theme - [Map of Maps](https://pietervdvn.github.io/MapComplete/index.html?layout=maps&z=14&lat=50.650&lon=4.2668#element), after a tweet -There are plenty more. Discover them in the app. +There are plenty more. [Discover them in the app](https://mapcomplete.osm.be/index.html). ### Statistics diff --git a/UI/Base/AsyncLazy.ts b/UI/Base/AsyncLazy.ts new file mode 100644 index 000000000..b8db53d3c --- /dev/null +++ b/UI/Base/AsyncLazy.ts @@ -0,0 +1,28 @@ +import BaseUIElement from "../BaseUIElement"; +import {VariableUiElement} from "./VariableUIElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Loading from "./Loading"; + +export default class AsyncLazy extends BaseUIElement{ + private readonly _f: () => Promise; + + constructor(f: () => Promise) { + super(); + this._f = f; + } + + protected InnerConstructElement(): HTMLElement { + // The caching of the BaseUIElement will guarantee that _f will only be called once + + return new VariableUiElement( + UIEventSource.FromPromise(this._f()).map(el => { + if(el === undefined){ + return new Loading() + } + return el + }) + + ).ConstructElement() + } + +} \ No newline at end of file diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index 32cdadbd6..4a878684e 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -19,6 +19,7 @@ export interface MinimapOptions { export interface MinimapObj { readonly leafletMap: UIEventSource, installBounds(factor: number | BBox, showRange?: boolean) : void + TakeScreenshot(): Promise; } export default class Minimap { diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 9ec90bd31..271b5d5cf 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -8,6 +8,8 @@ import * as L from "leaflet"; import {Map} from "leaflet"; import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; import {BBox} from "../../Logic/BBox"; +import 'leaflet-polylineoffset' +import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; export default class MinimapImplementation extends BaseUIElement implements MinimapObj { private static _nextId = 0; @@ -277,4 +279,10 @@ export default class MinimapImplementation extends BaseUIElement implements Mini this.leafletMap.setData(map) } + + public async TakeScreenshot(){ + const screenshotter = new SimpleMapScreenshoter(); + screenshotter.addTo(this.leafletMap.data); + return await screenshotter.takeScreen('image') + } } \ No newline at end of file diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index b9f134565..248f9a884 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -19,6 +19,7 @@ import Img from "./Img"; export default class ScrollableFullScreen extends UIElement { private static readonly empty = new FixedUiElement(""); private static _currentlyOpen: ScrollableFullScreen; + private hashToShow: string; public isShown: UIEventSource; private _component: BaseUIElement; private _fullscreencomponent: BaseUIElement; @@ -28,6 +29,7 @@ export default class ScrollableFullScreen extends UIElement { isShown: UIEventSource = new UIEventSource(false) ) { super(); + this.hashToShow = hashToShow; this.isShown = isShown; if (hashToShow === undefined) { @@ -45,24 +47,25 @@ export default class ScrollableFullScreen extends UIElement { self.Activate(); Hash.hash.setData(hashToShow) } else { - ScrollableFullScreen.clear(); + self.clear(); } }) Hash.hash.addCallback(hash => { - if (hash === hashToShow) { - return + if (!isShown.data) { + return; + } + if (hash === undefined || hash === "") { + isShown.setData(false) } - isShown.setData(false) }) } - private static clear() { + private clear() { ScrollableFullScreen.empty.AttachTo("fullscreen") const fs = document.getElementById("fullscreen"); ScrollableFullScreen._currentlyOpen?.isShown?.setData(false); fs.classList.add("hidden") - Hash.hash.setData(undefined); } InnerRender(): BaseUIElement { diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts index f911c0b41..e151df6c5 100644 --- a/UI/Base/TabbedComponent.ts +++ b/UI/Base/TabbedComponent.ts @@ -21,6 +21,9 @@ export class TabbedComponent extends Combine { let element = elements[i]; const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i)) openedTabSrc.addCallbackAndRun(selected => { + if(selected >= elements.length){ + selected = 0 + } if (selected === i) { header.SetClass("tab-active") header.RemoveClass("tab-non-active") diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index a18b25972..80cb7b26e 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -45,6 +45,7 @@ export default abstract class BaseUIElement { * Adds all the relevant classes, space separated */ public SetClass(clss: string) { + if(clss == undefined){return } const all = clss.split(" ").map(clsName => clsName.trim()); let recordedChange = false; for (let c of all) { diff --git a/UI/BigComponents/AddNewMarker.ts b/UI/BigComponents/AddNewMarker.ts index 09210bb52..e51152c7d 100644 --- a/UI/BigComponents/AddNewMarker.ts +++ b/UI/BigComponents/AddNewMarker.ts @@ -16,12 +16,12 @@ export default class AddNewMarker extends Combine { const layer = filteredLayer.layerDef; for (const preset of filteredLayer.layerDef.presets) { const tags = TagUtils.KVtoProperties(preset.tags) - const icon = layer.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html + const icon = layer.mapRendering[0].GenerateLeafletStyle(new UIEventSource(tags), false).html .SetClass("block relative") .SetStyle("width: 42px; height: 42px;"); icons.push(icon) if (last === undefined) { - last = layer.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html + last = layer.mapRendering[0].GenerateLeafletStyle(new UIEventSource(tags), false).html .SetClass("block relative") .SetStyle("width: 42px; height: 42px;"); } diff --git a/UI/BigComponents/Attribution.ts b/UI/BigComponents/Attribution.ts index 1bcdcbc9e..267bd776c 100644 --- a/UI/BigComponents/Attribution.ts +++ b/UI/BigComponents/Attribution.ts @@ -56,6 +56,7 @@ export default class Attribution extends Combine { ) ) super([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]); + this.SetClass("flex") } diff --git a/UI/BigComponents/AttributionPanel.ts b/UI/BigComponents/CopyrightPanel.ts similarity index 50% rename from UI/BigComponents/AttributionPanel.ts rename to UI/BigComponents/CopyrightPanel.ts index c18c8d287..bcf7f3784 100644 --- a/UI/BigComponents/AttributionPanel.ts +++ b/UI/BigComponents/CopyrightPanel.ts @@ -12,23 +12,99 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import * as contributors from "../../assets/contributors.json" import BaseUIElement from "../BaseUIElement"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import Title from "../Base/Title"; +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import {BBox} from "../../Logic/BBox"; +import Loc from "../../Models/Loc"; +import Toggle from "../Input/Toggle"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import Constants from "../../Models/Constants"; /** * The attribution panel shown on mobile */ -export default class AttributionPanel extends Combine { +export default class CopyrightPanel extends Combine { - private static LicenseObject = AttributionPanel.GenerateLicenses(); + private static LicenseObject = CopyrightPanel.GenerateLicenses(); - constructor(layoutToUse: LayoutConfig, contributions: UIEventSource>) { + constructor(state: { + layoutToUse: LayoutConfig, + featurePipeline: FeaturePipeline, + currentBounds: UIEventSource, + locationControl: UIEventSource, + osmConnection: OsmConnection + }, contributions: UIEventSource>) { + + const t =Translations.t.general.attribution + const layoutToUse = state.layoutToUse + const josmState = new UIEventSource(undefined) + // Reset after 15s + josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined)) + const iconStyle = "height: 1.5rem; width: auto" + const actionButtons = [ + new SubtleButton(Svg.liberapay_ui().SetStyle(iconStyle), t.donate, { + url: "https://liberapay.com/pietervdvn/", + newTab: true + }), + new SubtleButton(Svg.bug_ui().SetStyle(iconStyle), t.openIssueTracker, { + url: "https://github.com/pietervdvn/MapComplete/issues", + newTab: true + }), + new SubtleButton(Svg.statistics_ui().SetStyle(iconStyle), t.openOsmcha.Subs({theme: state.layoutToUse.title}), { + url: Utils.OsmChaLinkFor(31, state.layoutToUse.id), + newTab: true + }), + new VariableUiElement(state.locationControl.map(location => { + const idLink = `https://www.openstreetmap.org/edit?editor=id#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}` + return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, {url: idLink, newTab: true}) + })), + + new VariableUiElement(state.locationControl.map(location => { + const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}` + return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, {url: mapillaryLink, newTab: true}) + })), + new VariableUiElement(josmState.map(state => { + if(state === undefined){ + return undefined + } + state = state.toUpperCase() + if(state === "OK"){ + return t.josmOpened.SetClass("thanks") + } + return t.josmNotOpened.SetClass("alert") + })), + new Toggle( + new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle) , t.editJosm).onClick(() => { + const bounds: any = state.currentBounds.data; + if (bounds === undefined) { + return undefined + } + const top = bounds.getNorth(); + const bottom = bounds.getSouth(); + const right = bounds.getEast(); + const left = bounds.getWest(); + const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` + Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR")) + }), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible)), + + ].map(button => button.SetStyle("max-height: 3rem")) + + const iconAttributions = Utils.NoNull(Array.from(layoutToUse.ExtractImages())) + .map(CopyrightPanel.IconAttribution) + + let maintainer : BaseUIElement= undefined + if(layoutToUse.maintainer !== undefined && layoutToUse.maintainer !== "" && layoutToUse.maintainer.toLowerCase() !== "mapcomplete"){ + maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer}) + } + super([ Translations.t.general.attribution.attributionContent, - ((layoutToUse.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer}), - layoutToUse.credits, - "
", + maintainer, + new Combine(actionButtons).SetClass("block w-full"), + new FixedUiElement(layoutToUse.credits), new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.currentBounds), - "
", - new VariableUiElement(contributions.map(contributions => { if(contributions === undefined){ return "" @@ -62,14 +138,12 @@ export default class AttributionPanel extends Combine { })), - "
", - AttributionPanel.CodeContributors(), - "

", Translations.t.general.attribution.iconAttribution.title.Clone().SetClass("pt-6 pb-3"), "

", - ...Utils.NoNull(Array.from(layoutToUse.ExtractImages())) - .map(AttributionPanel.IconAttribution) - ]); + CopyrightPanel.CodeContributors(), + new Title(t.iconAttribution.title, 3), + ...iconAttributions + ].map(e => e?.SetClass("mt-4"))); this.SetClass("flex flex-col link-underline overflow-hidden") - this.SetStyle("max-width: calc(100vw - 5em); width: 40rem;") + this.SetStyle("max-width: calc(100vw - 5em); width: 40rem; margin-left: 0.75rem; margin-right: 0.5rem") } private static CodeContributors(): BaseUIElement { @@ -97,7 +171,7 @@ export default class AttributionPanel extends Combine { iconPath = "." + new URL(iconPath).pathname; } - const license: SmallLicense = AttributionPanel.LicenseObject[iconPath] + const license: SmallLicense = CopyrightPanel.LicenseObject[iconPath] if (license == undefined) { return undefined; } diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index 71506c398..3bbfccb90 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -42,9 +42,8 @@ export default class FilterView extends VariableUiElement { ); const name: Translation = config.config.name; - const styledNameChecked = name.Clone().SetStyle("font-size:large;padding-left:1.25rem"); - - const styledNameUnChecked = name.Clone().SetStyle("font-size:large;padding-left:1.25rem"); + const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-2"); + const styledNameUnChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-2"); const zoomStatus = new Toggle( @@ -82,6 +81,8 @@ export default class FilterView extends VariableUiElement { const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem;flex-shrink: 0;"; const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); + const layer = filteredLayer.layerDef + const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle( iconStyle ); @@ -95,9 +96,9 @@ export default class FilterView extends VariableUiElement { filteredLayer.layerDef.name ); - const styledNameChecked = name.Clone().SetStyle("font-size:large;padding-left:1.25rem"); + const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-3"); - const styledNameUnChecked = name.Clone().SetStyle("font-size:large;padding-left:1.25rem"); + const styledNameUnChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-3"); const zoomStatus = new Toggle( @@ -111,11 +112,14 @@ export default class FilterView extends VariableUiElement { const style = "display:flex;align-items:center;padding:0.5rem 0;"; - const layerChecked = new Combine([icon, styledNameChecked, zoomStatus]) + const layerIcon = layer.defaultIcon()?.SetClass("w-8 h-8 ml-2") + const layerIconUnchecked = layer.defaultIcon()?.SetClass("opacity-50 w-8 h-8 ml-2") + + const layerChecked = new Combine([icon, layerIcon, styledNameChecked, zoomStatus]) .SetStyle(style) .onClick(() => filteredLayer.isDisplayed.setData(false)); - const layerNotChecked = new Combine([iconUnselected, styledNameUnChecked]) + const layerNotChecked = new Combine([iconUnselected, layerIconUnchecked, styledNameUnChecked]) .SetStyle(style) .onClick(() => filteredLayer.isDisplayed.setData(true)); diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index 7e2b4d8fb..da16326e4 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -14,6 +14,9 @@ import Toggle from "../Input/Toggle"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {Utils} from "../../Utils"; import UserRelatedState from "../../Logic/State/UserRelatedState"; +import Loc from "../../Models/Loc"; +import BaseLayer from "../../Models/BaseLayer"; +import FilteredLayer from "../../Models/FilteredLayer"; export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { @@ -24,7 +27,10 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { layoutToUse: LayoutConfig, osmConnection: OsmConnection, featureSwitchShareScreen: UIEventSource, - featureSwitchMoreQuests: UIEventSource + featureSwitchMoreQuests: UIEventSource, + locationControl: UIEventSource, + backgroundLayer: UIEventSource, + filteredLayers: UIEventSource } & UserRelatedState) { const layoutToUse = state.layoutToUse; super( @@ -39,7 +45,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { layoutToUse: LayoutConfig, osmConnection: OsmConnection, featureSwitchShareScreen: UIEventSource, - featureSwitchMoreQuests: UIEventSource + featureSwitchMoreQuests: UIEventSource, + locationControl: UIEventSource, backgroundLayer: UIEventSource, filteredLayers: UIEventSource } & UserRelatedState, isShown: UIEventSource): { header: string | BaseUIElement; content: BaseUIElement }[] { @@ -56,7 +63,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { ] if (state.featureSwitchShareScreen.data) { - tabs.push({header: Svg.share_img, content: new ShareScreen()}); + tabs.push({header: Svg.share_img, content: new ShareScreen(state)}); } if (state.featureSwitchMoreQuests.data) { @@ -77,7 +84,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { layoutToUse: LayoutConfig, osmConnection: OsmConnection, featureSwitchShareScreen: UIEventSource, - featureSwitchMoreQuests: UIEventSource + featureSwitchMoreQuests: UIEventSource, + locationControl: UIEventSource, backgroundLayer: UIEventSource, filteredLayers: UIEventSource } & UserRelatedState, currentTab: UIEventSource, isShown: UIEventSource) { const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown) diff --git a/UI/BigComponents/ImportButton.ts b/UI/BigComponents/ImportButton.ts index 197f1f122..fd35a72db 100644 --- a/UI/BigComponents/ImportButton.ts +++ b/UI/BigComponents/ImportButton.ts @@ -4,27 +4,208 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import {VariableUiElement} from "../Base/VariableUIElement"; import Translations from "../i18n/Translations"; -import State from "../../State"; import Constants from "../../Models/Constants"; import Toggle from "../Input/Toggle"; import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; import {Tag} from "../../Logic/Tags/Tag"; import Loading from "../Base/Loading"; +import CreateNewWayAction from "../../Logic/Osm/Actions/CreateNewWayAction"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import {Changes} from "../../Logic/Osm/Changes"; +import {ElementStorage} from "../../Logic/ElementStorage"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import Lazy from "../Base/Lazy"; +import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; +import {PresetInfo} from "./SimpleAddUI"; +import Img from "../Base/Img"; +import {Translation} from "../i18n/Translation"; +import FilteredLayer from "../../Models/FilteredLayer"; +import SpecialVisualizations, {SpecialVisualization} from "../SpecialVisualizations"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Svg from "../../Svg"; +import {Utils} from "../../Utils"; +import Minimap from "../Base/Minimap"; +import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; +import AllKnownLayers from "../../Customizations/AllKnownLayers"; +import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; +import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; +import BaseLayer from "../../Models/BaseLayer"; +import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction"; + + +export interface ImportButtonState { + description?: Translation; + image: () => BaseUIElement, + message: string | BaseUIElement, + originalTags: UIEventSource, + newTags: UIEventSource, + targetLayer: FilteredLayer, + feature: any, + minZoom: number, + state: { + backgroundLayer: UIEventSource; + filteredLayers: UIEventSource; + featureSwitchUserbadge: UIEventSource; + featurePipeline: FeaturePipeline; + allElements: ElementStorage; + selectedElement: UIEventSource; + layoutToUse: LayoutConfig, + osmConnection: OsmConnection, + changes: Changes, + locationControl: UIEventSource<{ zoom: number }> + }, + guiState: { filterViewIsOpened: UIEventSource }, + + snapSettings?: { + snapToLayers: string[], + snapToLayersMaxDist?: number + }, + conflationSettings?: { + conflateWayId: string + } +} + +export class ImportButtonSpecialViz implements SpecialVisualization { + funcName = "import_button" + docs = `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. + +#### Importing a dataset into OpenStreetMap: requirements + +If you want to import a dataset, make sure that: + +1. The dataset to import has a suitable license +2. The community has been informed of the import +3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed + +There are also some technicalities in your theme to keep in mind: + +1. The new feature will be added and will flow through the program as any other new point as if it came from OSM. + This means that there should be a layer which will match the new tags and which will display it. +2. The original feature from your geojson layer will gain the tag '_imported=yes'. + This should be used to change the appearance or even to hide it (eg by changing the icon size to zero) +3. There should be a way for the theme to detect previously imported points, even after reloading. + A reference number to the original dataset is an excellent way to do this +4. When importing ways, the theme creator is also responsible of avoiding overlapping ways. + +#### Disabled in unofficial themes + +The import button can be tested in an unofficial theme by adding \`test=true\` or \`backend=osm-test\` as [URL-paramter](URL_Parameters.md). +The import button will show up then. If in testmode, you can read the changeset-XML directly in the web console. +In the case that MapComplete is pointed to the testing grounds, the edit will be made on ${OsmConnection.oauth_configs["osm-test"].url} + + +#### Specifying which tags to copy or add + +The argument \`tags\` of the import button takes a \`;\`-seperated list of tags to add. + +${Utils.Special_visualizations_tagsToApplyHelpText} + + +` + args = [ + { + name: "targetLayer", + doc: "The id of the layer where this point should end up. This is not very strict, it will simply result in checking that this layer is shown preventing possible duplicate elements" + }, + { + name: "tags", + doc: "The tags to add onto the new object - see specification above" + }, + { + name: "text", + doc: "The text to show on the button", + defaultValue: "Import this data into OpenStreetMap" + }, + { + name: "icon", + doc: "A nice icon to show in the button", + defaultValue: "./assets/svg/addSmall.svg" + }, + { + name: "minzoom", + doc: "How far the contributor must zoom in before being able to import the point", + defaultValue: "18" + }, { + name: "Snap onto layer(s)/replace geometry with this other way", + doc: " - If the value corresponding with this key starts with 'way/' and the feature is a LineString or Polygon, the original OSM-way geometry will be changed to match the new geometry\n" + + " - 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", + }, { + name: "snap max distance", + doc: "The maximum distance that this point will move to snap onto a layer (in meters)", + defaultValue: "5" + }] + + constr(state, tagSource, args, guiState) { + if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) { + return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"), + new FixedUiElement("To test, add test=true or backend=osm-test to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")]) + } + const newTags = SpecialVisualizations.generateTagsToApply(args[1], tagSource) + const id = tagSource.data.id; + const feature = state.allElements.ContainingFeatures.get(id) + let minZoom = args[4] == "" ? 18 : Number(args[4]) + if (isNaN(minZoom)) { + console.warn("Invalid minzoom:", minZoom) + minZoom = 18 + } + const message = args[2] + const imageUrl = args[3] + let img: () => BaseUIElement + const targetLayer: FilteredLayer = state.filteredLayers.data.filter(fl => fl.layerDef.id === args[0])[0] + + if (imageUrl !== undefined && imageUrl !== "") { + img = () => new Img(imageUrl) + } else { + img = () => Svg.add_ui() + } + + let snapSettings = undefined + let conflationSettings = undefined + const possibleWayId = tagSource.data[args[5]] + if (possibleWayId?.startsWith("way/")) { + // This is a conflation + conflationSettings = { + conflateWayId: possibleWayId + } + } else { + + + const snapToLayers = args[5]?.split(";").filter(s => s !== "") + const snapToLayersMaxDist = Number(args[6] ?? 6) + + if (targetLayer === undefined) { + const e = "Target layer not defined: error in import button for theme: " + state.layoutToUse.id + ": layer " + args[0] + " not found" + console.error(e) + return new FixedUiElement(e).SetClass("alert") + } + snapSettings = { + snapToLayers, + snapToLayersMaxDist + } + } + + return new ImportButton( + { + state, guiState, image: img, + feature, newTags, message, minZoom, + originalTags: tagSource, + targetLayer, + snapSettings, + conflationSettings + } + ); + } +} export default class ImportButton extends Toggle { - constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, - originalTags: UIEventSource, - newTags: UIEventSource, - lat: number, lon: number, - minZoom: number, - state: { - locationControl: UIEventSource<{ zoom: number }> - }) { + + constructor(o: ImportButtonState) { const t = Translations.t.general.add; - const isImported = originalTags.map(tags => tags._imported === "yes") + const isImported = o.originalTags.map(tags => tags._imported === "yes") const appliedTags = new Toggle( new VariableUiElement( - newTags.map(tgs => { + o.newTags.map(tgs => { const parts = [] for (const tag of tgs) { parts.push(tag.key + "=" + tag.value) @@ -32,53 +213,219 @@ export default class ImportButton extends Toggle { const txt = parts.join(" & ") return t.presetInfo.Subs({tags: txt}).SetClass("subtle") })), undefined, - State.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt) + o.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt) ) - const button = new SubtleButton(imageUrl, message) + const button = new SubtleButton(o.image(), o.message) - minZoom = Math.max(16, minZoom ?? 19) + o.minZoom = Math.max(16, o.minZoom ?? 19) - button.onClick(async () => { - if (isImported.data) { - return - } - originalTags.data["_imported"] = "yes" - originalTags.ping() // will set isImported as per its definition - const newElementAction = new CreateNewNodeAction(newTags.data, lat, lon, { - theme: State.state.layoutToUse.id, - changeType: "import" - }) - await State.state.changes.applyAction(newElementAction) - State.state.selectedElement.setData(State.state.allElements.ContainingFeatures.get( - newElementAction.newElementId - )) - console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get( - newElementAction.newElementId - )) - - - }) const withLoadingCheck = new Toggle(new Toggle( - new Loading(t.stillLoading.Clone()), - new Combine([button, appliedTags]).SetClass("flex flex-col"), - State.state.featurePipeline.runningQuery - ),t.zoomInFurther.Clone(), - state.locationControl.map(l => l.zoom >= minZoom) - ) + new Loading(t.stillLoading.Clone()), + new Combine([button, appliedTags]).SetClass("flex flex-col"), + o.state.featurePipeline.runningQuery + ), t.zoomInFurther.Clone(), + o.state.locationControl.map(l => l.zoom >= o.minZoom) + ) const importButton = new Toggle(t.hasBeenImported, withLoadingCheck, isImported) + + const importClicked = new UIEventSource(false); + const importFlow = new Toggle( + ImportButton.createConfirmPanel(o, isImported, importClicked), + importButton, + importClicked + ) + + button.onClick(() => { + importClicked.setData(true); + }) + + const pleaseLoginButton = new Toggle(t.pleaseLogin.Clone() - .onClick(() => State.state.osmConnection.AttemptLogin()) + .onClick(() => o.state.osmConnection.AttemptLogin()) .SetClass("login-button-friendly"), undefined, - State.state.featureSwitchUserbadge) - + o.state.featureSwitchUserbadge) - super(importButton, - pleaseLoginButton, - State.state.osmConnection.isLoggedIn + + super(new Toggle(importFlow, + pleaseLoginButton, + o.state.osmConnection.isLoggedIn + ), + t.wrongType, + new UIEventSource(ImportButton.canBeImported(o.feature)) ) } + + public static createConfirmPanel(o: ImportButtonState, + isImported: UIEventSource, + importClicked: UIEventSource) { + const geometry = o.feature.geometry + if (geometry.type === "Point") { + return new Lazy(() => ImportButton.createConfirmPanelForPoint(o, isImported, importClicked)) + } + + + if (geometry.type === "Polygon" || geometry.type == "LineString") { + return new Lazy(() => ImportButton.createConfirmForWay(o, isImported, importClicked)) + } + console.error("Invalid type to import", geometry.type) + return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert") + + + } + + public static createConfirmForWay(o: ImportButtonState, + isImported: UIEventSource, + importClicked: UIEventSource): BaseUIElement { + + const confirmationMap = Minimap.createMiniMap({ + allowMoving: false, + background: o.state.backgroundLayer + }) + confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl") + + const relevantFeatures = Utils.NoNull([o.feature, o.state.allElements?.ContainingFeatures?.get(o.conflationSettings?.conflateWayId)]) + // SHow all relevant data - including (eventually) the way of which the geometry will be replaced + new ShowDataMultiLayer({ + leafletMap: confirmationMap.leafletMap, + enablePopups: false, + zoomToFeatures: true, + features: new StaticFeatureSource(relevantFeatures, false), + allElements: o.state.allElements, + layers: o.state.filteredLayers + }) + + const theme = o.state.layoutToUse.id + + + const changes = o.state.changes + let confirm: () => Promise + if (o.conflationSettings !== undefined) { + + let replaceGeometryAction = new ReplaceGeometryAction( + o.state, + o.feature, + o.conflationSettings.conflateWayId, + { + theme: o.state.layoutToUse.id, + newTags: o.newTags.data + } + ) + + replaceGeometryAction.GetPreview().then(changePreview => { + new ShowDataLayer({ + leafletMap: confirmationMap.leafletMap, + enablePopups: false, + zoomToFeatures: false, + features: changePreview, + allElements: o.state.allElements, + layerToShow: AllKnownLayers.sharedLayers.get("conflation") + }) + }) + + confirm = async () => { + changes.applyAction (replaceGeometryAction) + return o.feature.properties.id + } + + } else { + confirm = async () => { + const geom = o.feature.geometry + let coordinates: [number, number][] + if (geom.type === "LineString") { + coordinates = geom.coordinates + } else if (geom.type === "Polygon") { + coordinates = geom.coordinates[0] + } + const action = new CreateNewWayAction(o.newTags.data, coordinates.map(lngLat => ({ + lat: lngLat[1], + lon: lngLat[0] + })), {theme}) + return action.newElementId + } + } + + + const confirmButton = new SubtleButton(o.image(), o.message) + confirmButton.onClick(async () => { + { + if (isImported.data) { + return + } + o.originalTags.data["_imported"] = "yes" + o.originalTags.ping() // will set isImported as per its definition + + const idToSelect = await confirm() + + o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(idToSelect)) + + } + }) + + const cancel = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel).onClick(() => { + importClicked.setData(false) + }) + + + return new Combine([confirmationMap, confirmButton, cancel]).SetClass("flex flex-col") + } + + public static createConfirmPanelForPoint( + o: ImportButtonState, + isImported: UIEventSource, + importClicked: UIEventSource): BaseUIElement { + + async function confirm() { + if (isImported.data) { + return + } + o.originalTags.data["_imported"] = "yes" + o.originalTags.ping() // will set isImported as per its definition + const geometry = o.feature.geometry + const lat = geometry.coordinates[1] + const lon = geometry.coordinates[0]; + const newElementAction = new CreateNewNodeAction(o.newTags.data, lat, lon, { + theme: o.state.layoutToUse.id, + changeType: "import" + }) + + await o.state.changes.applyAction(newElementAction) + o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get( + newElementAction.newElementId + )) + } + + function cancel() { + importClicked.setData(false) + } + + const presetInfo = { + tags: o.newTags.data, + icon: o.image, + description: o.description, + layerToAddTo: o.targetLayer, + name: o.message, + title: o.message, + preciseInput: { + snapToLayers: o.snapSettings?.snapToLayers, + maxSnapDistance: o.snapSettings?.snapToLayersMaxDist + } + } + + const [lon, lat] = o.feature.geometry.coordinates + return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), { + lon, + lat + }, confirm, cancel) + + } + + + private static canBeImported(feature: any) { + const type = feature.geometry.type + return type === "Point" || type === "LineString" || (type === "Polygon" && feature.geometry.coordinates.length === 1) + } } \ No newline at end of file diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index cc8b9e4c0..aff6b01f4 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -1,7 +1,7 @@ import Combine from "../Base/Combine"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import Translations from "../i18n/Translations"; -import AttributionPanel from "./AttributionPanel"; +import CopyrightPanel from "./CopyrightPanel"; import ContributorCount from "../../Logic/ContributorCount"; import Toggle from "../Input/Toggle"; import MapControlButton from "../MapControlButton"; @@ -14,6 +14,8 @@ import Loc from "../../Models/Loc"; import {BBox} from "../../Logic/BBox"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import FilteredLayer from "../../Models/FilteredLayer"; +import BaseLayer from "../../Models/BaseLayer"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; export default class LeftControls extends Combine { @@ -26,7 +28,9 @@ export default class LeftControls extends Combine { featureSwitchEnableExport: UIEventSource, featureSwitchExportAsPdf: UIEventSource, filteredLayers: UIEventSource, - featureSwitchFilter: UIEventSource + featureSwitchFilter: UIEventSource, + backgroundLayer: UIEventSource, + osmConnection: OsmConnection }, guiState: { downloadControlIsOpened: UIEventSource, @@ -37,8 +41,8 @@ export default class LeftControls extends Combine { const toggledCopyright = new ScrollableFullScreen( () => Translations.t.general.attribution.attributionTitle.Clone(), () => - new AttributionPanel( - state.layoutToUse, + new CopyrightPanel( + state, new ContributorCount(state).Contributors ), "copyright", diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index a8853a54f..352fc11e9 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -4,17 +4,30 @@ import MapControlButton from "../MapControlButton"; import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; import Svg from "../../Svg"; import MapState from "../../Logic/State/MapState"; +import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; +import AllKnownLayers from "../../Customizations/AllKnownLayers"; export default class RightControls extends Combine { constructor(state:MapState) { + + const geolocatioHandler = new GeoLocationHandler( + state.currentGPSLocation, + state.leafletMap, + state.layoutToUse + ) + + new ShowDataLayer({ + layerToShow: AllKnownLayers.sharedLayers.get("gps_location"), + leafletMap: state.leafletMap, + enablePopups: true, + features: geolocatioHandler.currentLocation + }) + const geolocationButton = new Toggle( new MapControlButton( - new GeoLocationHandler( - state.currentGPSLocation, - state.leafletMap, - state.layoutToUse - ), { + geolocatioHandler + , { dontStyle: true } ), diff --git a/UI/BigComponents/ShareScreen.ts b/UI/BigComponents/ShareScreen.ts index 32e1b0ce0..9fc7dde91 100644 --- a/UI/BigComponents/ShareScreen.ts +++ b/UI/BigComponents/ShareScreen.ts @@ -2,23 +2,21 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import {Translation} from "../i18n/Translation"; import Svg from "../../Svg"; import Combine from "../Base/Combine"; -import {SubtleButton} from "../Base/SubtleButton"; import {UIEventSource} from "../../Logic/UIEventSource"; import {Utils} from "../../Utils"; -import State from "../../State"; import Toggle from "../Input/Toggle"; -import {FixedUiElement} from "../Base/FixedUiElement"; import Translations from "../i18n/Translations"; -import Constants from "../../Models/Constants"; import BaseUIElement from "../BaseUIElement"; -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import Loc from "../../Models/Loc"; +import BaseLayer from "../../Models/BaseLayer"; +import FilteredLayer from "../../Models/FilteredLayer"; export default class ShareScreen extends Combine { - constructor(layout: LayoutConfig = undefined, layoutDefinition: string = undefined) { - layout = layout ?? State.state?.layoutToUse; - layoutDefinition = layoutDefinition ?? State.state?.layoutDefinition; + constructor(state: {layoutToUse: LayoutConfig, locationControl: UIEventSource, backgroundLayer: UIEventSource, filteredLayers: UIEventSource}) { + const layout = state?.layoutToUse; const tr = Translations.t.general.sharescreen; const optionCheckboxes: BaseUIElement[] = [] @@ -39,7 +37,7 @@ export default class ShareScreen extends Combine { ).ToggleOnClick() optionCheckboxes.push(includeLocation); - const currentLocation = State.state?.locationControl; + const currentLocation = state.locationControl; optionParts.push(includeLocation.isEnabled.map((includeL) => { if (currentLocation === undefined) { @@ -64,9 +62,8 @@ export default class ShareScreen extends Combine { } - if (State.state !== undefined) { - const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = State.state.backgroundLayer; + const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = state.backgroundLayer; const currentBackground = new VariableUiElement(currentLayer.map(layer => { return tr.fsIncludeCurrentBackgroundMap.Subs({name: layer?.name ?? ""}); })); @@ -94,13 +91,12 @@ export default class ShareScreen extends Combine { optionParts.push(includeLayerChoices.isEnabled.map((includeLayerSelection) => { if (includeLayerSelection) { - return Utils.NoNull(State.state.filteredLayers.data.map(fLayerToParam)).join("&") + return Utils.NoNull(state.filteredLayers.data.map(fLayerToParam)).join("&") } else { return null } - }, State.state.filteredLayers.data.map((flayer) => flayer.isDisplayed))); + }, state.filteredLayers.data.map((flayer) => flayer.isDisplayed))); - } const switches = [ {urlName: "fs-userbadge", human: tr.fsUserbadge}, @@ -148,56 +144,22 @@ export default class ShareScreen extends Combine { let literalText = `https://${host}${path}/${layout.id.toLowerCase()}` const parts = Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data))); - - let hash = ""; - if (layoutDefinition !== undefined) { - literalText = `https://${host}${path}/` - if (layout.id.startsWith("http")) { - parts.push("userlayout=" + encodeURIComponent(layout.id)) - } else { - hash = ("#" + layoutDefinition) - parts.push("userlayout=true"); - } - } - - if (parts.length === 0) { - return literalText + hash; + return literalText; } - - return literalText + "?" + parts.join("&") + hash; + return literalText + "?" + parts.join("&"); }, optionParts); const iframeCode = new VariableUiElement( url.map((url) => { return ` - <iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 25Opx; min-height: 250ox" title="${layout.title?.txt ?? "MapComplete"} with MapComplete"></iframe> + <iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" title="${layout.title?.txt ?? "MapComplete"} with MapComplete"></iframe> ` }) ); - let editLayout: BaseUIElement = new FixedUiElement(""); - if ((layoutDefinition !== undefined && State.state?.osmConnection !== undefined)) { - editLayout = - new VariableUiElement( - State.state.osmConnection.userDetails.map( - userDetails => { - if (userDetails.csCount <= Constants.userJourney.themeGeneratorReadOnlyUnlock) { - return ""; - } - - return new SubtleButton(Svg.pencil_ui(), - new Combine([tr.editThisTheme.Clone().SetClass("bold"), "
", - tr.editThemeDescription.Clone()]), - {url: `./customGenerator.html#${State.state.layoutDefinition}`, newTab: true}); - - } - )); - - } - const linkStatus = new UIEventSource(""); const link = new VariableUiElement( url.map((url) => ``) @@ -239,7 +201,6 @@ export default class ShareScreen extends Combine { super([ - editLayout, tr.intro.Clone(), link, new VariableUiElement(linkStatus), diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 84e14b682..f7931b853 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -12,18 +12,16 @@ import BaseUIElement from "../BaseUIElement"; import {VariableUiElement} from "../Base/VariableUIElement"; import Toggle from "../Input/Toggle"; import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection"; -import LocationInput from "../Input/LocationInput"; -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; import FilteredLayer from "../../Models/FilteredLayer"; -import {BBox} from "../../Logic/BBox"; import Loc from "../../Models/Loc"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {Changes} from "../../Logic/Osm/Changes"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; import {ElementStorage} from "../../Logic/ElementStorage"; +import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -33,8 +31,7 @@ import {ElementStorage} from "../../Logic/ElementStorage"; * - A 'read your unread messages before adding a point' */ -/*private*/ -interface PresetInfo extends PresetConfig { +export interface PresetInfo extends PresetConfig { name: string | BaseUIElement, icon: () => BaseUIElement, layerToAddTo: FilteredLayer @@ -91,20 +88,29 @@ export default class SimpleAddUI extends Toggle { if (preset === undefined) { return presetsOverview } - return SimpleAddUI.CreateConfirmButton(state, filterViewIsOpened, preset, - (tags, location, snapOntoWayId?: string) => { - if (snapOntoWayId === undefined) { - createNewPoint(tags, location, undefined) - } else { - OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD(way => { - createNewPoint(tags, location, way) - return true; - }) - } - }, - () => { - selectedPreset.setData(undefined) - }) + + + function confirm(tags, location, snapOntoWayId?: string) { + if (snapOntoWayId === undefined) { + createNewPoint(tags, location, undefined) + } else { + OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD(way => { + createNewPoint(tags, location, way) + return true; + }) + } + } + + function cancel() { + selectedPreset.setData(undefined) + } + + const message =Translations.t.general.add.addNew.Subs({category: preset.name}); + return new ConfirmLocationOfPoint(state, filterViewIsOpened, preset, + message, + state.LastClickLocation.data, + confirm, + cancel) } )) @@ -134,170 +140,7 @@ export default class SimpleAddUI extends Toggle { } - private static CreateConfirmButton( - state: { - LastClickLocation: UIEventSource<{ lat: number, lon: number }>, - osmConnection: OsmConnection, - featurePipeline: FeaturePipeline - }, - filterViewIsOpened: UIEventSource, - preset: PresetInfo, - confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void, - cancel: () => void): BaseUIElement { - - let location = state.LastClickLocation; - let preciseInput: LocationInput = undefined - if (preset.preciseInput !== undefined) { - // We uncouple the event source - const locationSrc = new UIEventSource({ - lat: location.data.lat, - lon: location.data.lon, - zoom: 19 - }); - - let backgroundLayer = undefined; - if (preset.preciseInput.preferredBackground) { - backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource(preset.preciseInput.preferredBackground)) - } - - let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined - let mapBounds: UIEventSource = undefined - if (preset.preciseInput.snapToLayers) { - snapToFeatures = new UIEventSource<{ feature: any }[]>([]) - mapBounds = new UIEventSource(undefined) - } - - - const tags = TagUtils.KVtoProperties(preset.tags ?? []); - preciseInput = new LocationInput({ - mapBackground: backgroundLayer, - centerLocation: locationSrc, - snapTo: snapToFeatures, - snappedPointTags: tags, - maxSnapDistance: preset.preciseInput.maxSnapDistance, - bounds: mapBounds - }) - preciseInput.installBounds(0.15, true) - preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") - - - if (preset.preciseInput.snapToLayers) { - // We have to snap to certain layers. - // Lets fetch them - - let loadedBbox: BBox = undefined - mapBounds?.addCallbackAndRunD(bbox => { - if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) { - // All is already there - // return; - } - - bbox = bbox.pad(2); - loadedBbox = bbox; - const allFeatures: { feature: any }[] = [] - preset.preciseInput.snapToLayers.forEach(layerId => { - state.featurePipeline.GetFeaturesWithin(layerId, bbox).forEach(feats => allFeatures.push(...feats.map(f => ({feature: f})))) - }) - snapToFeatures.setData(allFeatures) - }) - } - - } - - - let confirmButton: BaseUIElement = new SubtleButton(preset.icon(), - new Combine([ - Translations.t.general.add.addNew.Subs({category: preset.name}), - Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert") - ]).SetClass("flex flex-col") - ).SetClass("font-bold break-words") - .onClick(() => { - confirm(preset.tags, (preciseInput?.GetValue() ?? location).data, preciseInput?.snappedOnto?.data?.properties?.id); - }); - - if (preciseInput !== undefined) { - confirmButton = new Combine([preciseInput, confirmButton]) - } - - const openLayerControl = - new SubtleButton( - Svg.layers_ui(), - new Combine([ - Translations.t.general.add.layerNotEnabled - .Subs({layer: preset.layerToAddTo.layerDef.name}) - .SetClass("alert"), - Translations.t.general.add.openLayerControl - ]) - ) - .onClick(() => filterViewIsOpened.setData(true)) - - - const openLayerOrConfirm = new Toggle( - confirmButton, - openLayerControl, - preset.layerToAddTo.isDisplayed - ) - - const disableFilter = new SubtleButton( - new Combine([ - Svg.filter_ui().SetClass("absolute w-full"), - Svg.cross_bottom_right_svg().SetClass("absolute red-svg") - ]).SetClass("relative"), - new Combine( - [ - Translations.t.general.add.disableFiltersExplanation.Clone(), - Translations.t.general.add.disableFilters.Clone().SetClass("text-xl") - ] - ).SetClass("flex flex-col") - ).onClick(() => { - preset.layerToAddTo.appliedFilters.setData([]) - cancel() - }) - - const disableFiltersOrConfirm = new Toggle( - openLayerOrConfirm, - disableFilter, - preset.layerToAddTo.appliedFilters.map(filters => { - if (filters === undefined || filters.length === 0) { - return true; - } - for (const filter of filters) { - if (filter.selected === 0 && filter.filter.options.length === 1) { - return false; - } - if (filter.selected !== undefined) { - const tags = filter.filter.options[filter.selected].osmTags - if (tags !== undefined && tags["and"]?.length !== 0) { - // This actually doesn't filter anything at all - return false; - } - } - } - return true - - }) - ) - - - const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection); - - const cancelButton = new SubtleButton(Svg.close_ui(), - Translations.t.general.cancel - ).onClick(cancel) - - return new Combine([ - state.osmConnection.userDetails.data.dryRun ? - Translations.t.general.testing.Clone().SetClass("alert") : undefined, - disableFiltersOrConfirm, - cancelButton, - preset.description, - tagInfo - - ]).SetClass("flex flex-col") - - } - - private static CreateTagInfoFor(preset: PresetInfo, osmConnection: OsmConnection, optionallyLinkToWiki = true) { + public static CreateTagInfoFor(preset: PresetInfo, osmConnection: OsmConnection, optionallyLinkToWiki = true) { const csCount = osmConnection.userDetails.data.csCount; return new Toggle( Translations.t.general.add.presetInfo.Subs({ @@ -329,7 +172,7 @@ export default class SimpleAddUI extends Toggle { private static CreatePresetSelectButton(preset: PresetInfo, osmConnection: OsmConnection) { - const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, osmConnection ,false); + const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, osmConnection, false); return new SubtleButton( preset.icon(), new Combine([ @@ -368,7 +211,7 @@ export default class SimpleAddUI extends Toggle { for (const preset of presets) { const tags = TagUtils.KVtoProperties(preset.tags ?? []); - let icon: () => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html + let icon: () => BaseUIElement = () => layer.layerDef.mapRendering[0].GenerateLeafletStyle(new UIEventSource(tags), false).html .SetClass("w-12 h-12 block relative"); const presetInfo: PresetInfo = { tags: preset.tags, diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index e259b4f2e..0c93e4bf8 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -6,9 +6,6 @@ import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs"; import MapControlButton from "./MapControlButton"; import Svg from "../Svg"; import Toggle from "./Input/Toggle"; -import Hash from "../Logic/Web/Hash"; -import {QueryParameters} from "../Logic/Web/QueryParameters"; -import Constants from "../Models/Constants"; import UserBadge from "./BigComponents/UserBadge"; import SearchAndGo from "./BigComponents/SearchAndGo"; import Link from "./Base/Link"; @@ -24,76 +21,7 @@ import Translations from "./i18n/Translations"; import SimpleAddUI from "./BigComponents/SimpleAddUI"; import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; import Lazy from "./Base/Lazy"; - -export class DefaultGuiState { - public readonly welcomeMessageIsOpened : UIEventSource; - public readonly downloadControlIsOpened: UIEventSource; - public readonly filterViewIsOpened: UIEventSource; - public readonly copyrightViewIsOpened: UIEventSource; - public readonly welcomeMessageOpenedTab: UIEventSource - public readonly allFullScreenStates: UIEventSource[] = [] - - constructor() { - - - - this.welcomeMessageOpenedTab = UIEventSource.asFloat(QueryParameters.GetQueryParameter( - "tab", - "0", - `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` - )); - this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter( - "welcome-control-toggle", - "false", - "Whether or not the welcome panel is shown" - ) - this.downloadControlIsOpened = QueryParameters.GetBooleanQueryParameter( - "download-control-toggle", - "false", - "Whether or not the download panel is shown" - ) - this.filterViewIsOpened = QueryParameters.GetBooleanQueryParameter( - "filter-toggle", - "false", - "Whether or not the filter view is shown" - ) - this.copyrightViewIsOpened = QueryParameters.GetBooleanQueryParameter( - "copyright-toggle", - "false", - "Whether or not the copyright view is shown" - ) - if(Hash.hash.data === "download"){ - this.downloadControlIsOpened.setData(true) - } - if(Hash.hash.data === "filter"){ - this.filterViewIsOpened.setData(true) - } - if(Hash.hash.data === "copyright"){ - this.copyrightViewIsOpened.setData(true) - } - if(Hash.hash.data === "" || Hash.hash.data === undefined || Hash.hash.data === "welcome"){ - this.welcomeMessageIsOpened.setData(true) - } - - this.allFullScreenStates.push(this.downloadControlIsOpened, this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened) - - for (let i = 0; i < this.allFullScreenStates.length; i++){ - const fullScreenState = this.allFullScreenStates[i]; - for (let j = 0; j < this.allFullScreenStates.length; j++){ - if(i == j){ - continue - } - const otherState = this.allFullScreenStates[j]; - fullScreenState.addCallbackAndRunD(isOpened => { - if(isOpened){ - otherState.setData(false) - } - }) - } - } - - } -} +import {DefaultGuiState} from "./DefaultGuiState"; /** @@ -114,10 +42,8 @@ export default class DefaultGUI { Utils.LoadCustomCss(state.layoutToUse.customCss); } - this.SetupUIElements(); this.SetupMap() - } diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts new file mode 100644 index 000000000..7c5cae139 --- /dev/null +++ b/UI/DefaultGuiState.ts @@ -0,0 +1,74 @@ +import {UIEventSource} from "../Logic/UIEventSource"; +import {QueryParameters} from "../Logic/Web/QueryParameters"; +import Constants from "../Models/Constants"; +import Hash from "../Logic/Web/Hash"; + +export class DefaultGuiState { + public readonly welcomeMessageIsOpened: UIEventSource; + public readonly downloadControlIsOpened: UIEventSource; + public readonly filterViewIsOpened: UIEventSource; + public readonly copyrightViewIsOpened: UIEventSource; + public readonly welcomeMessageOpenedTab: UIEventSource + public readonly allFullScreenStates: UIEventSource[] = [] + static state: DefaultGuiState; + + constructor() { + + + this.welcomeMessageOpenedTab = UIEventSource.asFloat(QueryParameters.GetQueryParameter( + "tab", + "0", + `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` + )); + this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter( + "welcome-control-toggle", + "false", + "Whether or not the welcome panel is shown" + ) + this.downloadControlIsOpened = QueryParameters.GetBooleanQueryParameter( + "download-control-toggle", + "false", + "Whether or not the download panel is shown" + ) + this.filterViewIsOpened = QueryParameters.GetBooleanQueryParameter( + "filter-toggle", + "false", + "Whether or not the filter view is shown" + ) + this.copyrightViewIsOpened = QueryParameters.GetBooleanQueryParameter( + "copyright-toggle", + "false", + "Whether or not the copyright view is shown" + ) + if (Hash.hash.data === "download") { + this.downloadControlIsOpened.setData(true) + } + if (Hash.hash.data === "filters") { + this.filterViewIsOpened.setData(true) + } + if (Hash.hash.data === "copyright") { + this.copyrightViewIsOpened.setData(true) + } + if (Hash.hash.data === "" || Hash.hash.data === undefined || Hash.hash.data === "welcome") { + this.welcomeMessageIsOpened.setData(true) + } + + this.allFullScreenStates.push(this.downloadControlIsOpened, this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened) + + for (let i = 0; i < this.allFullScreenStates.length; i++) { + const fullScreenState = this.allFullScreenStates[i]; + for (let j = 0; j < this.allFullScreenStates.length; j++) { + if (i == j) { + continue + } + const otherState = this.allFullScreenStates[j]; + fullScreenState.addCallbackAndRunD(isOpened => { + if (isOpened) { + otherState.setData(false) + } + }) + } + } + + } +} \ No newline at end of file diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts index e030e30b4..e03103322 100644 --- a/UI/ExportPDF.ts +++ b/UI/ExportPDF.ts @@ -1,9 +1,6 @@ - - import jsPDF from "jspdf"; -import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; import {UIEventSource} from "../Logic/UIEventSource"; -import Minimap from "./Base/Minimap"; +import Minimap, {MinimapObj} from "./Base/Minimap"; import Loc from "../Models/Loc"; import BaseLayer from "../Models/BaseLayer"; import {FixedUiElement} from "./Base/FixedUiElement"; @@ -14,7 +11,6 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; import {BBox} from "../Logic/BBox"; -import ShowOverlayLayer from "./ShowDataLayer/ShowOverlayLayer"; /** * Creates screenshoter to take png screenshot * Creates jspdf and downloads it @@ -63,14 +59,12 @@ export default class ExportPDF { location: new UIEventSource(loc), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot background: options.background, allowMoving: false, - - - onFullyLoaded: leaflet => window.setTimeout(() => { + onFullyLoaded: _ => window.setTimeout(() => { if (self._screenhotTaken) { return; } try { - self.CreatePdf(leaflet) + self.CreatePdf(minimap) .then(() => self.cleanup()) .catch(() => self.cleanup()) } catch (e) { @@ -112,20 +106,17 @@ export default class ExportPDF { this._screenhotTaken = true; } - private async CreatePdf(leaflet: L.Map) { + private async CreatePdf(minimap: MinimapObj) { + + + console.log("PDF creation started") const t = Translations.t.general.pdf; const layout = this._layout - const screenshotter = new SimpleMapScreenshoter(); - //minimap op index.html -> hidden daar alles op doen en dan weg - //minimap - leaflet map ophalen - boundaries ophalen - State.state.featurePipeline - screenshotter.addTo(leaflet); - let doc = new jsPDF('landscape'); - - const image = (await screenshotter.takeScreen('image')) + const image = await minimap.TakeScreenshot() // @ts-ignore doc.addImage(image, 'PNG', 0, 0, this.mapW, this.mapH); diff --git a/UI/Input/InputElementMap.ts b/UI/Input/InputElementMap.ts index 548e50363..a2a50f9d3 100644 --- a/UI/Input/InputElementMap.ts +++ b/UI/Input/InputElementMap.ts @@ -25,8 +25,8 @@ export default class InputElementMap extends InputElement { const self = this; this._value = inputElement.GetValue().map( (t => { - const currentX = self.GetValue()?.data; const newX = toX(t); + const currentX = self.GetValue()?.data; if (isSame(currentX, newX)) { return currentX; } diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts index 4eb39d7e9..2875f4362 100644 --- a/UI/Input/LengthInput.ts +++ b/UI/Input/LengthInput.ts @@ -45,6 +45,7 @@ export default class LengthInput extends InputElement { background: this.background, allowMoving: false, location: this._location, + attribution:true, leafletOptions: { tap: true } diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 8e5474c33..21cfdebe7 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -6,7 +6,6 @@ import BaseLayer from "../../Models/BaseLayer"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; import State from "../../State"; -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import {GeoOperations} from "../../Logic/GeoOperations"; import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; @@ -16,7 +15,6 @@ import {FixedUiElement} from "../Base/FixedUiElement"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; import BaseUIElement from "../BaseUIElement"; import Toggle from "./Toggle"; -import {start} from "repl"; export default class LocationInput extends InputElement implements MinimapObj { @@ -25,12 +23,17 @@ export default class LocationInput extends InputElement implements MinimapO id: "matchpoint", source: { osmTags: {and: []} }, - icon: "./assets/svg/crosshair-empty.svg" + mapRendering: [{ + location: ["point"], + icon: "./assets/svg/crosshair-empty.svg" + }] }, "matchpoint icon", true ) - + IsSelected: UIEventSource = new UIEventSource(false); public readonly snappedOnto: UIEventSource = new UIEventSource(undefined) + public readonly _matching_layer: LayerConfig; + public readonly leafletMap: UIEventSource private _centerLocation: UIEventSource; private readonly mapBackground: UIEventSource; /** @@ -43,10 +46,7 @@ export default class LocationInput extends InputElement implements MinimapO private readonly _maxSnapDistance: number private readonly _snappedPointTags: any; private readonly _bounds: UIEventSource; - public readonly _matching_layer: LayerConfig; private readonly map: BaseUIElement & MinimapObj; - public readonly leafletMap: UIEventSource - private readonly clickLocation: UIEventSource; private readonly _minZoom: number; @@ -83,7 +83,7 @@ export default class LocationInput extends InputElement implements MinimapO } this._matching_layer = matchingLayer; } else { - this._matching_layer = LocationInput.matchLayer + this._matching_layer = LocationInput.matchLayer } this._snappedPoint = options.centerLocation.map(loc => { @@ -96,6 +96,8 @@ export default class LocationInput extends InputElement implements MinimapO let min = undefined; let matchedWay = undefined; for (const feature of self._snapTo.data ?? []) { + try{ + const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [loc.lon, loc.lat]) if (min === undefined) { min = nearestPointOnLine @@ -108,6 +110,9 @@ export default class LocationInput extends InputElement implements MinimapO matchedWay = feature.feature; } + }catch(e){ + console.log("Snapping to a nearest point failed for ", feature.feature,"due to ", e) + } } if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) { @@ -158,25 +163,32 @@ export default class LocationInput extends InputElement implements MinimapO IsValid(t: Loc): boolean { return t !== undefined; } - + + installBounds(factor: number | BBox, showRange?: boolean): void { + this.map.installBounds(factor, showRange) + } + TakeScreenshot(): Promise { + return this.map.TakeScreenshot() + } + protected InnerConstructElement(): HTMLElement { try { const self = this; const hasMoved = new UIEventSource(false) - const startLocation = { ...this._centerLocation.data } - this._centerLocation. addCallbackD(newLocation => { + const startLocation = {...this._centerLocation.data} + this._centerLocation.addCallbackD(newLocation => { const f = 100000 console.log(newLocation.lon, startLocation.lon) - const diff = (Math.abs(newLocation.lon * f - startLocation.lon* f ) + Math.abs(newLocation.lat* f - startLocation.lat* f )) - if(diff < 1){ + const diff = (Math.abs(newLocation.lon * f - startLocation.lon * f) + Math.abs(newLocation.lat * f - startLocation.lat * f)) + if (diff < 1) { return; } hasMoved.setData(true) return true; }) this.clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) - if (this._snapTo !== undefined) { - + if (this._snapTo !== undefined) { + // Show the lines to snap to new ShowDataMultiLayer({ features: new StaticFeatureSource(this._snapTo, true), @@ -184,7 +196,7 @@ export default class LocationInput extends InputElement implements MinimapO zoomToFeatures: false, leafletMap: this.map.leafletMap, layers: State.state.filteredLayers, - allElements: State.state.allElements + allElements: State.state.allElements } ) // Show the central point @@ -194,16 +206,16 @@ export default class LocationInput extends InputElement implements MinimapO } return [{feature: loc}]; }) - new ShowDataLayer({ - features: new StaticFeatureSource(matchPoint, true), - enablePopups: false, - zoomToFeatures: false, - leafletMap: this.map.leafletMap, - layerToShow: this._matching_layer, - allElements: State.state.allElements, - selectedElement: State.state.selectedElement - }) - + new ShowDataLayer({ + features: new StaticFeatureSource(matchPoint, true), + enablePopups: false, + zoomToFeatures: false, + leafletMap: this.map.leafletMap, + layerToShow: this._matching_layer, + allElements: State.state.allElements, + selectedElement: State.state.selectedElement + }) + } this.mapBackground.map(layer => { const leaflet = this.map.leafletMap.data @@ -216,19 +228,19 @@ export default class LocationInput extends InputElement implements MinimapO leaflet.setZoom(layer.max_zoom - 1) }, [this.map.leafletMap]) - + const animatedHand = Svg.hand_ui() .SetStyle("width: 2rem; height: unset;") .SetClass("hand-drag-animation block pointer-events-none") - + return new Combine([ new Combine([ Svg.move_arrows_ui() .SetClass("block relative pointer-events-none") .SetStyle("left: -2.5rem; top: -2.5rem; width: 5rem; height: 5rem") - ]).SetClass("block w-0 h-0 z-10 relative") + ]).SetClass("block w-0 h-0 z-10 relative") .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5"), - + new Toggle(undefined, animatedHand, hasMoved) .SetClass("block w-0 h-0 z-10 relative") @@ -244,9 +256,4 @@ export default class LocationInput extends InputElement implements MinimapO } } - - installBounds(factor: number | BBox, showRange?: boolean): void { - this.map.installBounds(factor, showRange) - } - } \ No newline at end of file diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index ad5554346..77cc5c433 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -19,6 +19,9 @@ import {FixedInputElement} from "./FixedInputElement"; import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"; import Wikidata from "../../Logic/Web/Wikidata"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; +import Table from "../Base/Table"; +import Combine from "../Base/Combine"; +import Title from "../Base/Title"; interface TextFieldDef { name: string, @@ -28,12 +31,163 @@ interface TextFieldDef { inputHelper?: (value: UIEventSource, options?: { location: [number, number], mapBackgroundLayer?: UIEventSource, - args: (string | number | boolean)[] + args: (string | number | boolean | any)[] feature?: any }) => InputElement, inputmode?: string } +class WikidataTextField implements TextFieldDef { + name = "wikidata" + explanation = + new Combine([ + "A wikidata identifier, e.g. Q42.", + new Title("Helper arguments"), + new Table(["name", "doc"], + [ + ["key", "the value of this tag will initialize search (default: name)"], + ["options", new Combine(["A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", + new Table( + ["subarg", "doc"], + [["removePrefixes", "remove these snippets of text from the start of the passed string to search"], + ["removePostfixes", "remove these snippets of text from the end of the passed string to search"], + ] + )]) + ]]), + new Title("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 + +\`\`\` +"freeform": { + "key": "name:etymology:wikidata", + "type": "wikidata", + "helperArgs": [ + "name", + { + "removePostfixes": [ + "street", + "boulevard", + "path", + "square", + "plaza", + ] + } + ] +} +\`\`\`` + ]).AsMarkdown() + + + public isValid(str) { + + if (str === undefined) { + return false; + } + if (str.length <= 2) { + return false; + } + return !str.split(";").some(str => Wikidata.ExtractKey(str) === undefined) + } + + public reformat(str) { + if (str === undefined) { + return undefined; + } + let out = str.split(";").map(str => Wikidata.ExtractKey(str)).join("; ") + if (str.endsWith(";")) { + out = out + ";" + } + return out; + } + + public inputHelper(currentValue, inputHelperOptions) { + const args = inputHelperOptions.args ?? [] + const searchKey = args[0] ?? "name" + + let searchFor = inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() + + const options = args[1] + if (searchFor !== undefined && options !== undefined) { + const prefixes = options["removePrefixes"] + const postfixes = options["removePostfixes"] + for (const postfix of postfixes ?? []) { + if (searchFor.endsWith(postfix)) { + searchFor = searchFor.substring(0, searchFor.length - postfix.length) + break; + } + } + + for (const prefix of prefixes ?? []) { + if (searchFor.startsWith(prefix)) { + searchFor = searchFor.substring(prefix.length) + break; + } + } + + } + + return new WikidataSearchBox({ + value: currentValue, + searchText: new UIEventSource(searchFor) + }) + } +} + +class OpeningHoursTextField implements TextFieldDef { + name = "opening_hours" + explanation = + new Combine([ + "Has extra elements to easily input when a POI is opened.", + new Title("Helper arguments"), + new Table(["name", "doc"], + [ + ["options", new Combine([ + "A JSON-object of type `{ prefix: string, postfix: string }`. ", + new Table(["subarg", "doc"], + [ + ["prefix", "Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse"], + ["postfix", "Piece of text that will always be added to the end of the generated opening hours"], + ]) + + ]) + ] + ]), + new Title("Example usage"), + "To add a conditional (based on time) access restriction:\n\n```\n" + ` +"freeform": { + "key": "access:conditional", + "type": "opening_hours", + "helperArgs": [ + { + "prefix":"no @ (", + "postfix":")" + } + ] +}` + "\n```\n\n*Don't forget to pass the prefix and postfix in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )`"]).AsMarkdown() + + + isValid() { + return true + } + + reformat(str) { + return str + } + + inputHelper(value: UIEventSource, inputHelperOptions: { + location: [number, number], + mapBackgroundLayer?: UIEventSource, + args: (string | number | boolean | any)[] + feature?: any + }) { + const args = (inputHelperOptions.args ?? [])[0] + const prefix = args?.prefix ?? "" + const postfix = args?.postfix ?? "" + + return new OpeningHoursInput(value, prefix, postfix) + } +} + export default class ValidatedTextField { public static tpList: TextFieldDef[] = [ @@ -117,7 +271,8 @@ export default class ValidatedTextField { if (args[0]) { zoom = Number(args[0]) if (isNaN(zoom)) { - throw "Invalid zoom level for argument at 'length'-input" + console.error("Invalid zoom level for argument at 'length'-input. The offending argument is: ",args[0]," (using 19 instead)") + zoom = 19 } } @@ -146,60 +301,7 @@ export default class ValidatedTextField { }, "decimal" ), - ValidatedTextField.tp( - "wikidata", - "A wikidata identifier, e.g. Q42. Input helper arguments: [ key: the value of this tag will initialize search (default: name), options: { removePrefixes: string[], removePostfixes: string[] } these prefixes and postfixes will be removed from the initial search value]", - (str) => { - if (str === undefined) { - return false; - } - if(str.length <= 2){ - return false; - } - return !str.split(";").some(str => Wikidata.ExtractKey(str) === undefined) - }, - (str) => { - if (str === undefined) { - return undefined; - } - let out = str.split(";").map(str => Wikidata.ExtractKey(str)).join("; ") - if(str.endsWith(";")){ - out = out + ";" - } - return out; - }, - (currentValue, inputHelperOptions) => { - const args = inputHelperOptions.args ?? [] - const searchKey = args[0] ?? "name" - - let searchFor = inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() - - const options = args[1] - if (searchFor !== undefined && options !== undefined) { - const prefixes = options["removePrefixes"] - const postfixes = options["removePostfixes"] - for (const postfix of postfixes ?? []) { - if (searchFor.endsWith(postfix)) { - searchFor = searchFor.substring(0, searchFor.length - postfix.length) - break; - } - } - - for (const prefix of prefixes ?? []) { - if (searchFor.startsWith(prefix)) { - searchFor = searchFor.substring(prefix.length) - break; - } - } - - } - - return new WikidataSearchBox({ - value: currentValue, - searchText: new UIEventSource(searchFor) - }) - } - ), + new WikidataTextField(), ValidatedTextField.tp( "int", @@ -299,15 +401,7 @@ export default class ValidatedTextField { undefined, "tel" ), - ValidatedTextField.tp( - "opening_hours", - "Has extra elements to easily input when a POI is opened", - () => true, - str => str, - (value) => { - return new OpeningHoursInput(value); - } - ), + new OpeningHoursTextField(), ValidatedTextField.tp( "color", "Shows a color picker", @@ -458,7 +552,11 @@ export default class ValidatedTextField { public static HelpText(): string { const explanations = ValidatedTextField.tpList.map(type => ["## " + type.name, "", type.explanation].join("\n")).join("\n\n") - return "# Available types for text fields\n\nThe listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them\n\n" + explanations + return new Combine([ + new Title("Available types for text fields", 1), + "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them", + explanations + ]).SetClass("flex flex-col").AsMarkdown() } private static tp(name: string, diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts new file mode 100644 index 000000000..68e380997 --- /dev/null +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -0,0 +1,184 @@ +import {UIEventSource} from "../../Logic/UIEventSource"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import BaseUIElement from "../BaseUIElement"; +import LocationInput from "../Input/LocationInput"; +import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; +import {BBox} from "../../Logic/BBox"; +import {TagUtils} from "../../Logic/Tags/TagUtils"; +import {SubtleButton} from "../Base/SubtleButton"; +import Combine from "../Base/Combine"; +import Translations from "../i18n/Translations"; +import Svg from "../../Svg"; +import Toggle from "../Input/Toggle"; +import SimpleAddUI, {PresetInfo} from "../BigComponents/SimpleAddUI"; + +export default class ConfirmLocationOfPoint extends Combine { + + + constructor( + state: { + osmConnection: OsmConnection, + featurePipeline: FeaturePipeline + }, + filterViewIsOpened: UIEventSource, + preset: PresetInfo, + confirmText: BaseUIElement, + loc: { lon: number, lat: number }, + confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void, + cancel: () => void, + ) { + + let preciseInput: LocationInput = undefined + if (preset.preciseInput !== undefined) { + // We uncouple the event source + const zloc = {...loc, zoom: 19} + const locationSrc = new UIEventSource(zloc); + + let backgroundLayer = undefined; + if (preset.preciseInput.preferredBackground) { + backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource(preset.preciseInput.preferredBackground)) + } + + let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined + let mapBounds: UIEventSource = undefined + if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { + snapToFeatures = new UIEventSource<{ feature: any }[]>([]) + mapBounds = new UIEventSource(undefined) + } + + + const tags = TagUtils.KVtoProperties(preset.tags ?? []); + preciseInput = new LocationInput({ + mapBackground: backgroundLayer, + centerLocation: locationSrc, + snapTo: snapToFeatures, + snappedPointTags: tags, + maxSnapDistance: preset.preciseInput.maxSnapDistance, + bounds: mapBounds + }) + preciseInput.installBounds(0.15, true) + preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") + + + if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { + // We have to snap to certain layers. + // Lets fetch them + + let loadedBbox: BBox = undefined + mapBounds?.addCallbackAndRunD(bbox => { + if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) { + // All is already there + // return; + } + + bbox = bbox.pad(2); + loadedBbox = bbox; + const allFeatures: { feature: any }[] = [] + preset.preciseInput.snapToLayers.forEach(layerId => { + console.log("Snapping to", layerId) + state.featurePipeline.GetFeaturesWithin(layerId, bbox)?.forEach(feats => allFeatures.push(...feats.map(f => ({feature: f})))) + }) + console.log("Snapping to", allFeatures) + snapToFeatures.setData(allFeatures) + }) + } + + } + + + let confirmButton: BaseUIElement = new SubtleButton(preset.icon(), + new Combine([ + confirmText, + Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert") + ]).SetClass("flex flex-col") + ).SetClass("font-bold break-words") + .onClick(() => { + confirm(preset.tags, (preciseInput?.GetValue()?.data ?? loc), preciseInput?.snappedOnto?.data?.properties?.id); + }); + + if (preciseInput !== undefined) { + confirmButton = new Combine([preciseInput, confirmButton]) + } + + const openLayerControl = + new SubtleButton( + Svg.layers_ui(), + new Combine([ + Translations.t.general.add.layerNotEnabled + .Subs({layer: preset.layerToAddTo.layerDef.name}) + .SetClass("alert"), + Translations.t.general.add.openLayerControl + ]) + ) + .onClick(() => filterViewIsOpened.setData(true)) + + + const openLayerOrConfirm = new Toggle( + confirmButton, + openLayerControl, + preset.layerToAddTo.isDisplayed + ) + + const disableFilter = new SubtleButton( + new Combine([ + Svg.filter_ui().SetClass("absolute w-full"), + Svg.cross_bottom_right_svg().SetClass("absolute red-svg") + ]).SetClass("relative"), + new Combine( + [ + Translations.t.general.add.disableFiltersExplanation.Clone(), + Translations.t.general.add.disableFilters.Clone().SetClass("text-xl") + ] + ).SetClass("flex flex-col") + ).onClick(() => { + preset.layerToAddTo.appliedFilters.setData([]) + cancel() + }) + + const disableFiltersOrConfirm = new Toggle( + openLayerOrConfirm, + disableFilter, + preset.layerToAddTo.appliedFilters.map(filters => { + if (filters === undefined || filters.length === 0) { + return true; + } + for (const filter of filters) { + if (filter.selected === 0 && filter.filter.options.length === 1) { + return false; + } + if (filter.selected !== undefined) { + const tags = filter.filter.options[filter.selected].osmTags + if (tags !== undefined && tags["and"]?.length !== 0) { + // This actually doesn't filter anything at all + return false; + } + } + } + return true + + }) + ) + + + const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection); + + const cancelButton = new SubtleButton(Svg.close_ui(), + Translations.t.general.cancel + ).onClick(cancel) + + super([ + state.osmConnection.userDetails.data.dryRun ? + Translations.t.general.testing.Clone().SetClass("alert") : undefined, + disableFiltersOrConfirm, + cancelButton, + preset.description, + tagInfo + + ]) + + this.SetClass("flex flex-col") + + } + +} \ No newline at end of file diff --git a/UI/OpeningHours/OpeningHoursInput.ts b/UI/OpeningHours/OpeningHoursInput.ts index d8d5ce16b..88b061a3d 100644 --- a/UI/OpeningHours/OpeningHoursInput.ts +++ b/UI/OpeningHours/OpeningHoursInput.ts @@ -23,11 +23,39 @@ export default class OpeningHoursInput extends InputElement { private readonly _value: UIEventSource; private readonly _element: BaseUIElement; - constructor(value: UIEventSource = new UIEventSource("")) { + constructor(value: UIEventSource = new UIEventSource(""), prefix = "", postfix = "") { super(); this._value = value; + let valueWithoutPrefix = value + if (prefix !== "" && postfix !== "") { + + valueWithoutPrefix = value.map(str => { + if (str === undefined) { + return undefined; + } + if(str === ""){ + return "" + } + if (str.startsWith(prefix) && str.endsWith(postfix)) { + return str.substring(prefix.length, str.length - postfix.length) + } + return str + }, [], noPrefix => { + if (noPrefix === undefined) { + return undefined; + } + if(noPrefix === ""){ + return "" + } + if (noPrefix.startsWith(prefix) && noPrefix.endsWith(postfix)) { + return noPrefix + } - const leftoverRules = value.map(str => { + return prefix + noPrefix + postfix + }) + } + + const leftoverRules = valueWithoutPrefix.map(str => { if (str === undefined) { return [] } @@ -45,9 +73,9 @@ export default class OpeningHoursInput extends InputElement { return leftOvers; }) // Note: MUST be bound AFTER the leftover rules! - const rulesFromOhPicker = value.map(OH.Parse); + const rulesFromOhPicker = valueWithoutPrefix.map(OH.Parse); - const ph = value.map(str => { + const ph = valueWithoutPrefix.map(str => { if (str === undefined) { return "" } @@ -68,7 +96,7 @@ export default class OpeningHoursInput extends InputElement { ...leftoverRules.data, ph.data ] - value.setData(Utils.NoEmpty(rules).join(";")); + valueWithoutPrefix.setData(Utils.NoEmpty(rules).join(";")); } rulesFromOhPicker.addCallback(update); diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts index 785d046a9..9a2a30800 100644 --- a/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/UI/OpeningHours/OpeningHoursVisualization.ts @@ -23,11 +23,23 @@ export default class OpeningHoursVisualization extends Toggle { Translations.t.general.weekdays.abbreviations.sunday, ] - constructor(tags: UIEventSource, key: string) { + constructor(tags: UIEventSource, key: string, prefix = "", postfix = "") { const tagsDirect = tags.data; const ohTable = new VariableUiElement(tags - .map(tags => tags[key]) // This mapping will absorb all other changes to tags in order to prevent regeneration + .map(tags => { + const value: string = tags[key]; + if (value === undefined) { + return undefined + } + if (value.startsWith(prefix) && value.endsWith(postfix)) { + return value.substring(prefix.length, value.length - postfix.length).trim() + } + return value; + }) // This mapping will absorb all other changes to tags in order to prevent regeneration .map(ohtext => { + if (ohtext === undefined) { + return new FixedUiElement("No opening hours defined with key " + key).SetClass("alert") + } try { // noinspection JSPotentiallyInvalidConstructorUsage const oh = new opening_hours(ohtext, { @@ -35,12 +47,12 @@ export default class OpeningHoursVisualization extends Toggle { lon: tagsDirect._lon, address: { country_code: tagsDirect._country - } - }, {tag_key: key}); + }, + }, {tag_key: "opening_hours"}); return OpeningHoursVisualization.CreateFullVisualisation(oh) } catch (e) { - console.log(e); + console.warn(e, e.stack); return new Combine([Translations.t.general.opening_hours.error_loading, new Toggle( new FixedUiElement(e).SetClass("subtle"), diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 478737a9e..b0981f78b 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -264,7 +264,7 @@ export default class DeleteWizard extends Toggle { ] - }, undefined, "Delete wizard" + }, "Delete wizard" ) } diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index 2be62b01b..be9634b2f 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -16,7 +16,10 @@ export default class EditableTagRendering extends Toggle { constructor(tags: UIEventSource, configuration: TagRenderingConfig, units: Unit [], - editMode = new UIEventSource(false) + options:{ + editMode? : UIEventSource , + innerElementClasses?: string + } ) { // The tagrendering is hidden if: @@ -27,7 +30,12 @@ export default class EditableTagRendering extends Toggle { (configuration?.condition?.matchesProperties(tags) ?? true)) super( - new Lazy(() => EditableTagRendering.CreateRendering(tags, configuration, units, editMode)), + new Lazy(() => { + const editMode = options.editMode ?? new UIEventSource(false) + const rendering = EditableTagRendering.CreateRendering(tags, configuration, units, editMode); + rendering.SetClass(options.innerElementClasses) + return rendering + }), undefined, renderingIsShown ) @@ -71,7 +79,6 @@ export default class EditableTagRendering extends Toggle { editMode ) } - rendering.SetClass("block w-full break-word text-default m-1 p-1 border-b border-gray-200 mb-2 pb-2") return rendering; } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index d46ebf711..3ec287856 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -5,7 +5,6 @@ import Combine from "../Base/Combine"; import TagRenderingAnswer from "./TagRenderingAnswer"; import State from "../../State"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; -import {Tag} from "../../Logic/Tags/Tag"; import Constants from "../../Models/Constants"; import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; import BaseUIElement from "../BaseUIElement"; @@ -37,7 +36,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { private static GenerateTitleBar(tags: UIEventSource, layerConfig: LayerConfig): BaseUIElement { - const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI", undefined)) + const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI")) .SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2"); const titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, @@ -52,26 +51,57 @@ export default class FeatureInfoBox extends ScrollableFullScreen { private static GenerateContent(tags: UIEventSource, layerConfig: LayerConfig): BaseUIElement { - let questionBox: BaseUIElement = undefined; + let questionBoxes: Map = new Map(); + const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map(tr => tr.group)) if (State.state.featureSwitchUserbadge.data) { - questionBox = new QuestionBox(tags, layerConfig.tagRenderings, layerConfig.units); + for (const groupName of allGroupNames) { + const questions = layerConfig.tagRenderings.filter(tr => tr.group === groupName) + const questionBox = new QuestionBox(tags, questions, layerConfig.units); + questionBoxes.set(groupName, questionBox) + } } - let questionBoxIsUsed = false; - const renderings: BaseUIElement[] = layerConfig.tagRenderings.map(tr => { - if (tr.question === null) { - // This is the question box! - questionBoxIsUsed = true; - return questionBox; + const allRenderings = [] + for (let i = 0; i < allGroupNames.length; i++) { + const groupName = allGroupNames[i]; + + const trs = layerConfig.tagRenderings.filter(tr => tr.group === groupName) + const renderingsForGroup: (EditableTagRendering | BaseUIElement)[] = [] + const innerClasses = "block w-full break-word text-default m-1 p-1 border-b border-gray-200 mb-2 pb-2"; + for (const tr of trs) { + if (tr.question === null || tr.id === "questions") { + // This is a question box! + const questionBox = questionBoxes.get(tr.group) + questionBoxes.delete(tr.group) + renderingsForGroup.push(questionBox) + } else { + let classes = innerClasses + let isHeader = renderingsForGroup.length === 0 && i > 0 + if(isHeader){ + // This is the first element of a group! + // It should act as header and be sticky + classes= "" + } + + const etr = new EditableTagRendering(tags, tr, layerConfig.units,{ + innerElementClasses: innerClasses + }) + if(isHeader){ + etr.SetClass("sticky top-0") + } + renderingsForGroup.push(etr) + } } - return new EditableTagRendering(tags, tr, layerConfig.units); - }); + + allRenderings.push(...renderingsForGroup) + } + let editElements: BaseUIElement[] = [] - if (!questionBoxIsUsed) { + questionBoxes.forEach(questionBox => { editElements.push(questionBox); - } + }) if (layerConfig.allowMove) { editElements.push( @@ -83,7 +113,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { layerConfig.allowMove ); }) - ) + ).SetClass("text-base") ); } @@ -94,20 +124,20 @@ export default class FeatureInfoBox extends ScrollableFullScreen { id, layerConfig.deletion )) - )) + ).SetClass("text-base")) } if (layerConfig.allowSplit) { editElements.push( new VariableUiElement(tags.map(tags => tags.id).map(id => new SplitRoadWizard(id)) - )) + ).SetClass("text-base")) } const hasMinimap = layerConfig.tagRenderings.some(tr => FeatureInfoBox.hasMinimap(tr)) if (!hasMinimap) { - renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) + allRenderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) } editElements.push( @@ -132,7 +162,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { new VariableUiElement( State.state.featureSwitchIsDebugging.map(isDebugging => { if (isDebugging) { - const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, new Tag("id", ""), ""); + const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, ""); return new TagRenderingAnswer(tags, config, "all_tags") } }) @@ -147,10 +177,9 @@ export default class FeatureInfoBox extends ScrollableFullScreen { return new Combine(editElements).SetClass("flex flex-col") } )) - renderings.push(editors) + allRenderings.push(editors) - - return new Combine(renderings).SetClass("block") + return new Combine(allRenderings).SetClass("block") } /** diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index 1192ff8ee..5dfa062ea 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -16,6 +16,7 @@ import Lazy from "../Base/Lazy"; export default class QuestionBox extends VariableUiElement { constructor(tagsSource: UIEventSource, tagRenderings: TagRenderingConfig[], units: Unit[]) { + const skippedQuestions: UIEventSource = new UIEventSource([]) tagRenderings = tagRenderings @@ -33,7 +34,7 @@ export default class QuestionBox extends VariableUiElement { { units: units, afterSave: () => { - // We save + // We save and indicate progress by pinging and recalculating skippedQuestions.ping(); }, cancelButton: Translations.t.general.skip.Clone() @@ -45,7 +46,7 @@ export default class QuestionBox extends VariableUiElement { } ))); - const skippedQuestionsButton = Translations.t.general.skippedQuestions.Clone() + const skippedQuestionsButton = Translations.t.general.skippedQuestions .onClick(() => { skippedQuestions.setData([]); }) diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 9c321d8b9..9ee39f683 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -21,8 +21,13 @@ export default class SplitRoadWizard extends Toggle { private static splitLayerStyling = new LayerConfig({ id: "splitpositions", source: {osmTags: "_cutposition=yes"}, - icon: {render: "circle:white;./assets/svg/scissors.svg"}, - iconSize: {render: "30,30,center"}, + mapRendering: [ + { + location: ["point","centroid"], + icon: {render: "circle:white;./assets/svg/scissors.svg"}, + iconSize: {render: "30,30,center"} + } + ], }, "(BUILTIN) SplitRoadWizard.ts", true) public dialogIsOpened: UIEventSource @@ -61,7 +66,7 @@ export default class SplitRoadWizard extends Toggle { miniMap.installBounds(BBox.get(roadElement).pad(0.25), false) // Define how a cut is displayed on the map - + // Datalayer displaying the road and the cut points (if any) new ShowDataLayer({ features: new StaticFeatureSource(splitPoints, true), diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index a45172a98..9c0c530ab 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -9,7 +9,6 @@ import CheckBoxes from "../Input/Checkboxes"; import InputElementMap from "../Input/InputElementMap"; import {SaveButton} from "./SaveButton"; import State from "../../State"; -import {Changes} from "../../Logic/Osm/Changes"; import {VariableUiElement} from "../Base/VariableUIElement"; import Translations from "../i18n/Translations"; import {FixedUiElement} from "../Base/FixedUiElement"; @@ -49,7 +48,7 @@ export default class TagRenderingQuestion extends Combine { const applicableMappingsSrc = UIEventSource.ListStabilized(tags.map(tags => { - const applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[] = [] + const applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = [] for (const mapping of configuration.mappings ?? []) { if (mapping.hideInAnswer === true) { continue @@ -85,7 +84,7 @@ export default class TagRenderingQuestion extends Combine { const save = () => { const selection = inputElement.GetValue().data; if (selection) { - (State.state?.changes ?? new Changes()) + (State.state?.changes) .applyAction(new ChangeTagAction( tags.data.id, selection, tags.data, { theme: State.state?.layoutToUse?.id ?? "unkown", @@ -108,9 +107,9 @@ export default class TagRenderingQuestion extends Combine { const saveButton = new Combine([ options.saveButtonConstr(inputElement.GetValue()), - new Toggle(Translations.t.general.testing, undefined, State.state.featureSwitchIsTesting).SetClass("alert") + new Toggle(Translations.t.general.testing.SetClass("alert"), undefined, State.state.featureSwitchIsTesting) ]) - + let bottomTags: BaseUIElement; if (options.bottomText !== undefined) { bottomTags = options.bottomText(inputElement.GetValue()) @@ -147,7 +146,7 @@ export default class TagRenderingQuestion extends Combine { private static GenerateInputElement(configuration: TagRenderingConfig, - applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[], + applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter, addExtraTags: Tag[] }[], applicableUnit: Unit, tagsSource: UIEventSource) : InputElement { @@ -341,12 +340,16 @@ export default class TagRenderingQuestion extends Combine { mapping: { if: TagsFilter, then: Translation, + addExtraTags: Tag[] }, ifNot?: TagsFilter[]): InputElement { let tagging: TagsFilter = mapping.if; if (ifNot !== undefined) { tagging = new And([mapping.if, ...ifNot]) } + if (mapping.addExtraTags) { + tagging = new And([tagging, ...mapping.addExtraTags]) + } return new FixedInputElement( new SubstitutedTranslation(mapping.then, tagsSource), diff --git a/UI/ShowDataLayer/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts index 2f957ed15..497c9282b 100644 --- a/UI/ShowDataLayer/ShowDataLayer.ts +++ b/UI/ShowDataLayer/ShowDataLayer.ts @@ -1,20 +1,31 @@ -/** - * The data layer shows all the given geojson elements with the appropriate icon etc - */ + import {UIEventSource} from "../../Logic/UIEventSource"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import FeatureInfoBox from "../Popup/FeatureInfoBox"; import {ShowDataLayerOptions} from "./ShowDataLayerOptions"; import {ElementStorage} from "../../Logic/ElementStorage"; +import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"; +/* +// import 'leaflet-polylineoffset'; +We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object. + Even though actually importing this here would seem cleaner, we don't do this as this breaks some scripts: + - Scripts are ran in ts-node + - ts-node doesn't define the 'window'-object + - Importing this will execute some code which needs the window object + */ + +/** + * The data layer shows all the given geojson elements with the appropriate icon etc + */ export default class ShowDataLayer { private readonly _leafletMap: UIEventSource; private readonly _enablePopups: boolean; - private readonly _features: UIEventSource<{ feature: any }[]> + private readonly _features: RenderingMultiPlexerFeatureSource private readonly _layerToShow: LayerConfig; private readonly _selectedElement: UIEventSource - private readonly allElements : ElementStorage + private readonly allElements: ElementStorage // Used to generate a fresh ID when needed private _cleanCount = 0; private geoLayer = undefined; @@ -29,7 +40,7 @@ export default class ShowDataLayer { */ private readonly leafletLayersPerId = new Map() - private readonly showDataLayerid : number; + private readonly showDataLayerid: number; private static dataLayerIds = 0 constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) { @@ -41,8 +52,7 @@ export default class ShowDataLayer { console.error("Invalid ShowDataLayer invocation: options.features is undefed") throw "Invalid ShowDataLayer invocation: options.features is undefed" } - const features = options.features.features.map(featFreshes => featFreshes.map(ff => ff.feature)); - this._features = features; + this._features = new RenderingMultiPlexerFeatureSource(options.features, options.layerToShow); this._layerToShow = options.layerToShow; this._selectedElement = options.selectedElement this.allElements = options.allElements; @@ -53,7 +63,7 @@ export default class ShowDataLayer { } ); - features.addCallback(_ => self.update(options)); + this._features.features.addCallback(_ => self.update(options)); options.doShowLayer?.addCallback(doShow => { const mp = options.leafletMap.data; if (mp == undefined) { @@ -103,13 +113,13 @@ export default class ShowDataLayer { leafletLayer.openPopup() } }) - + this.update(options) } private update(options: ShowDataLayerOptions) { - if (this._features.data === undefined) { + if (this._features.features.data === undefined) { return; } this.isDirty = true; @@ -139,13 +149,39 @@ export default class ShowDataLayer { onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer) }); - const allFeats = this._features.data; + const allFeats = this._features.features.data; for (const feat of allFeats) { if (feat === undefined) { continue } try { - this.geoLayer.addData(feat); + if ((feat.geometry.type === "LineString" || feat.geometry.type === "MultiLineString")) { + const self = this; + const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates) + const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource(feat.properties); + let offsettedLine; + tagsSource + .map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags)) + .withEqualityStabilized((a, b) => { + if(a === b){ + return true + } + if(a === undefined || b === undefined){ + return false + } + return a.offset === b.offset && a.color === b.color && a.weight === b.weight && a.dashArray === b.dashArray + }) + .addCallbackAndRunD(lineStyle => { + if (offsettedLine !== undefined) { + self.geoLayer.removeLayer(offsettedLine) + } + offsettedLine = L.polyline(coords, lineStyle); + this.postProcessFeature(feat, offsettedLine) + offsettedLine.addTo(this.geoLayer) + }) + } else { + this.geoLayer.addData(feat); + } } catch (e) { console.error("Could not add ", feat, "to the geojson layer in leaflet due to", e, e.stack) } @@ -153,9 +189,10 @@ export default class ShowDataLayer { if (options.zoomToFeatures ?? false) { try { - mp.fitBounds(this.geoLayer.getBounds(), {animate: false}) + const bounds = this.geoLayer.getBounds() + mp.fitBounds(bounds, {animate: false}) } catch (e) { - console.error(e) + console.debug("Invalid bounds",e) } } @@ -170,7 +207,21 @@ export default class ShowDataLayer { const tagsSource = this.allElements?.addOrGetElement(feature) ?? new UIEventSource(feature.properties); // Every object is tied to exactly one layer const layer = this._layerToShow - return layer?.GenerateLeafletStyle(tagsSource, true); + + const pointRenderingIndex = feature.pointRenderingIndex + const lineRenderingIndex = feature.lineRenderingIndex + + if (pointRenderingIndex !== undefined) { + const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups) + return { + icon: style + } + } + if (lineRenderingIndex !== undefined) { + return layer.lineRendering[lineRenderingIndex].GenerateLeafletStyle(tagsSource.data) + } + + throw "Neither lineRendering nor mapRendering defined for " + feature } private pointToLayer(feature, latLng): L.Layer { @@ -182,23 +233,16 @@ export default class ShowDataLayer { if (layer === undefined) { return; } - let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties) - const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) - const style = layer.GenerateLeafletStyle(tagSource, clickable); - const baseElement = style.icon.html; + const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) && this._enablePopups + let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(tagSource, clickable); + const baseElement = style.html; if (!this._enablePopups) { baseElement.SetStyle("cursor: initial !important") } + style.html = style.html.ConstructElement() return L.marker(latLng, { - icon: L.divIcon({ - html: baseElement.ConstructElement(), - className: style.icon.className, - iconAnchor: style.icon.iconAnchor, - iconUrl: style.icon.iconUrl ?? "./assets/svg/bug.svg", - popupAnchor: style.icon.popupAnchor, - iconSize: style.icon.iconSize - }) + icon: L.divIcon(style) }); } @@ -228,7 +272,7 @@ export default class ShowDataLayer { let infobox: FeatureInfoBox = undefined; - const id = `popup-${feature.properties.id}-${feature.geometry.type}-${this.showDataLayerid}-${this._cleanCount}` + const id = `popup-${feature.properties.id}-${feature.geometry.type}-${this.showDataLayerid}-${this._cleanCount}-${feature.pointRenderingIndex ?? feature.lineRenderingIndex}` popup.setContent(`
Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading
`) leafletLayer.on("popupopen", () => { if (infobox === undefined) { @@ -237,7 +281,6 @@ export default class ShowDataLayer { infobox.isShown.addCallback(isShown => { if (!isShown) { - this._selectedElement?.setData(undefined); leafletLayer.closePopup() } }); @@ -249,14 +292,14 @@ export default class ShowDataLayer { } }); - + // Add the feature to the index to open the popup when needed this.leafletLayersPerId.set(feature.properties.id + feature.geometry.type, { feature: feature, leafletlayer: leafletLayer }) - + } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 188215296..674b44499 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -20,7 +20,7 @@ import Histogram from "./BigComponents/Histogram"; import Loc from "../Models/Loc"; import {Utils} from "../Utils"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; -import ImportButton from "./BigComponents/ImportButton"; +import {ImportButtonSpecialViz} from "./BigComponents/ImportButton"; import {Tag} from "../Logic/Tags/Tag"; import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; @@ -29,10 +29,22 @@ import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; import WikipediaBox from "./Wikipedia/WikipediaBox"; import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; import MultiApply from "./Popup/MultiApply"; +import AllKnownLayers from "../Customizations/AllKnownLayers"; +import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; +import Link from "./Base/Link"; +import List from "./Base/List"; +import {OsmConnection} from "../Logic/Osm/OsmConnection"; +import {SubtleButton} from "./Base/SubtleButton"; +import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction"; +import {And} from "../Logic/Tags/And"; +import Toggle from "./Input/Toggle"; +import Img from "./Base/Img"; +import FilteredLayer from "../Models/FilteredLayer"; +import {DefaultGuiState} from "./DefaultGuiState"; export interface SpecialVisualization { funcName: string, - constr: ((state: State, tagSource: UIEventSource, argument: string[]) => BaseUIElement), + constr: ((state: State, tagSource: UIEventSource, argument: string[], guistate: DefaultGuiState) => BaseUIElement), docs: string, example?: string, args: { name: string, defaultValue?: string, doc: string }[] @@ -40,6 +52,7 @@ export interface SpecialVisualization { export default class SpecialVisualizations { + static tagsToApplyHelpText = Utils.Special_visualizations_tagsToApplyHelpText public static specialVisualizations: SpecialVisualization[] = [ { @@ -49,7 +62,7 @@ export default class SpecialVisualizations { constr: ((state: State, tags: UIEventSource) => { const calculatedTags = [].concat( SimpleMetaTagger.lazyTags, - ... state.layoutToUse.layers.map(l => l.calculatedTags?.map(c => c[0]) ?? [])) + ...state.layoutToUse.layers.map(l => l.calculatedTags?.map(c => c[0]) ?? [])) return new VariableUiElement(tags.map(tags => { const parts = []; for (const key in tags) { @@ -57,20 +70,20 @@ export default class SpecialVisualizations { continue } let v = tags[key] - if(v === ""){ + if (v === "") { v = "empty string" } parts.push([key, v ?? "undefined"]); } - - for(const key of calculatedTags){ + + for (const key of calculatedTags) { const value = tags[key] - if(value === undefined){ + if (value === undefined) { continue } - parts.push([ ""+key+"", value ]) + parts.push(["" + key + "", value]) } - + return new Table( ["key", "value"], parts @@ -88,7 +101,7 @@ export default class SpecialVisualizations { }], constr: (state: State, tags, args) => { let imagePrefixes: string[] = undefined; - if(args.length > 0){ + if (args.length > 0) { imagePrefixes = [].concat(...args.map(a => a.split(","))); } return new ImageCarousel(AllImageProviders.LoadImagesFor(tags, imagePrefixes), tags, imagePrefixes); @@ -101,9 +114,9 @@ export default class SpecialVisualizations { name: "image-key", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", defaultValue: "image" - },{ - name:"label", - doc:"The text to show on the button", + }, { + name: "label", + doc: "The text to show on the button", defaultValue: "Add image" }], constr: (state: State, tags, args) => { @@ -125,17 +138,16 @@ export default class SpecialVisualizations { new VariableUiElement( tagsSource.map(tags => tags[args[0]]) .map(wikidata => { - const wikidatas : string[] = + const wikidatas: string[] = Utils.NoEmpty(wikidata?.split(";")?.map(wd => wd.trim()) ?? []) return new WikipediaBox(wikidatas) }) - ) - + }, { funcName: "minimap", - docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div", + docs: "A small map showing the selected feature.", args: [ { doc: "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", @@ -214,6 +226,53 @@ export default class SpecialVisualizations { ) + minimap.SetStyle("overflow: hidden; pointer-events: none;") + return minimap; + } + }, + { + funcName: "sided_minimap", + docs: "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", + args: [ + { + doc: "The side to show, either `left` or `right`", + name: "side", + } + ], + example: "`{sided_minimap(left)}`", + constr: (state, tagSource, args) => { + + const properties = tagSource.data; + const locationSource = new UIEventSource({ + lat: Number(properties._lat), + lon: Number(properties._lon), + zoom: 18 + }) + const minimap = Minimap.createMiniMap( + { + background: state.backgroundLayer, + location: locationSource, + allowMoving: false + } + ) + const side = args[0] + const feature = state.allElements.ContainingFeatures.get(tagSource.data.id) + const copy = {...feature} + copy.properties = { + id: side + } + new ShowDataLayer( + { + leafletMap: minimap["leafletMap"], + enablePopups: false, + zoomToFeatures: true, + layerToShow: AllKnownLayers.sharedLayers.get("left_right_style"), + features: new StaticFeatureSource([copy], false), + allElements: State.state.allElements + } + ) + + minimap.SetStyle("overflow: hidden; pointer-events: none;") return minimap; } @@ -253,9 +312,18 @@ export default class SpecialVisualizations { name: "key", defaultValue: "opening_hours", doc: "The tagkey from which the table is constructed." + }, { + name: "prefix", + defaultValue: "", + doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__" + }, { + name: "postfix", + defaultValue: "", + doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__" }], + example: "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)}`", constr: (state: State, tagSource: UIEventSource, args) => { - return new OpeningHoursVisualization(tagSource, args[0]) + return new OpeningHoursVisualization(tagSource, args[0], args[1], args[2]) } }, { @@ -415,86 +483,25 @@ export default class SpecialVisualizations { ) } }, + new ImportButtonSpecialViz(), { - funcName: "import_button", - args: [ - { - name: "tags", - doc: "Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber=$number`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags)" - }, - { - name: "text", - doc: "The text to show on the button", - defaultValue: "Import this data into OpenStreetMap" - }, - { - name: "icon", - doc: "A nice icon to show in the button", - defaultValue: "./assets/svg/addSmall.svg" - }, - {name:"minzoom", - doc: "How far the contributor must zoom in before being able to import the point", - defaultValue: "18"}], - docs: `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. - -If you want to import a dataset, make sure that: - -1. The dataset to import has a suitable license -2. The community has been informed of the import -3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed - -There are also some technicalities in your theme to keep in mind: - -1. The new point will be added and will flow through the program as any other new point as if it came from OSM. - This means that there should be a layer which will match the new tags and which will display it. -2. The original point from your geojson layer will gain the tag '_imported=yes'. - This should be used to change the appearance or even to hide it (eg by changing the icon size to zero) -3. There should be a way for the theme to detect previously imported points, even after reloading. - A reference number to the original dataset is an excellen way to do this -`, - constr: (state, tagSource, args) => { - if (!state.layoutToUse.official && !state.featureSwitchIsTesting.data) { - return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"), - new FixedUiElement("To test, add 'test=true' to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")]) - } - const tgsSpec = args[0].split(",").map(spec => { - const kv = spec.split("=").map(s => s.trim()); - if (kv.length != 2) { - throw "Invalid key spec: multiple '=' found in " + spec - } - return kv - }) - const rewrittenTags: UIEventSource = tagSource.map(tags => { - const newTags: Tag [] = [] - for (const [key, value] of tgsSpec) { - if (value.startsWith('$')) { - const origKey = value.substring(1) - newTags.push(new Tag(key, tags[origKey])) - } else { - newTags.push(new Tag(key, value)) - } - } - return newTags - }) - const id = tagSource.data.id; - const feature = state.allElements.ContainingFeatures.get(id) - if (feature.geometry.type !== "Point") { - return new FixedUiElement("Error: can only import point objects").SetClass("alert") - } - const [lon, lat] = feature.geometry.coordinates; - return new ImportButton( - args[2], args[1], tagSource, rewrittenTags, lat, lon, Number(args[3]), state - ) - } - }, - {funcName: "multi_apply", + funcName: "multi_apply", docs: "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", - args:[ + args: [ {name: "feature_ids", doc: "A JSOn-serialized list of IDs of features to apply the tagging on"}, - {name: "keys", doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features." }, + { + name: "keys", + doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features." + }, {name: "text", doc: "The text to show on the button"}, - {name:"autoapply",doc:"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"}, - {name:"overwrite",doc:"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"} + { + name: "autoapply", + doc: "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" + }, + { + name: "overwrite", + doc: "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: "{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)}", constr: (state, tagsSource, args) => { @@ -503,14 +510,14 @@ There are also some technicalities in your theme to keep in mind: const text = args[2] const autoapply = args[3]?.toLowerCase() === "true" const overwrite = args[4]?.toLowerCase() === "true" - const featureIds : UIEventSource = tagsSource.map(tags => { - const ids = tags[featureIdsKey] - try{ - if(ids === undefined){ + const featureIds: UIEventSource = tagsSource.map(tags => { + const ids = tags[featureIdsKey] + try { + if (ids === undefined) { return [] } return JSON.parse(ids); - }catch(e){ + } catch (e) { console.warn("Could not parse ", ids, "as JSON to extract IDS which should be shown on the map.") return [] } @@ -526,14 +533,115 @@ There are also some technicalities in your theme to keep in mind: state } ); - + + } + }, + { + funcName: "tag_apply", + docs: "Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" + SpecialVisualizations.tagsToApplyHelpText, + args: [ + { + name: "tags_to_apply", + doc: "A specification of the tags to apply" + }, + { + name: "message", + doc: "The text to show to the contributor" + }, + { + name: "image", + doc: "An image to show to the contributor on the button" + }, + { + name: "id_of_object_to_apply_this_one", + defaultValue: undefined, + doc: "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: "`{tag_apply(survey_date:=$_now:date, Surveyed today!)}`", + constr: (state, tags, args) => { + const tagsToApply = SpecialVisualizations.generateTagsToApply(args[0], tags) + const msg = args[1] + let image = args[2]?.trim() + if (image === "" || image === "undefined") { + image = undefined + } + const targetIdKey = args[3] + const t = Translations.t.general.apply_button + + const tagsExplanation = new VariableUiElement(tagsToApply.map(tagsToApply => { + const tagsStr = tagsToApply.map(t => t.asHumanString(false, true)).join("&"); + let el: BaseUIElement = new FixedUiElement(tagsStr) + if(targetIdKey !== undefined){ + const targetId = tags.data[targetIdKey] ?? tags.data.id + el = t.appliedOnAnotherObject.Subs({tags: tagsStr , id: targetId }) + } + return el; + } + )).SetClass("subtle") + + const applied = new UIEventSource(false) + const applyButton = new SubtleButton(image, new Combine([msg, tagsExplanation]).SetClass("flex flex-col")) + .onClick(() => { + const targetId = tags.data[ targetIdKey] ?? tags.data.id + const changeAction = new ChangeTagAction(targetId, + new And(tagsToApply.data), + tags.data, // We pass in the tags of the selected element, not the tags of the target element! + { + theme: state.layoutToUse.id, + changeType: "answer" + } + ) + state.changes.applyAction(changeAction) + applied.setData(true) + }) + + + return new Toggle( + new Toggle( + t.isApplied.SetClass("thanks"), + applyButton, + applied + ) + , undefined, state.osmConnection.isLoggedIn) } } ] - static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); + static generateTagsToApply(spec: string, tagSource: UIEventSource): UIEventSource { - private static GenHelpMessage() { + const tgsSpec = spec.split(";").map(spec => { + const kv = spec.split("=").map(s => s.trim()); + if (kv.length != 2) { + throw "Invalid key spec: multiple '=' found in " + spec + } + return kv + }) + return tagSource.map(tags => { + const newTags: Tag [] = [] + for (const [key, value] of tgsSpec) { + if (value.indexOf('$') >= 0) { + + let parts = value.split("$") + // THe first of the split won't start with a '$', so no substitution needed + let actualValue = parts[0] + parts.shift() + + for (const part of parts) { + const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/) + actualValue += (tags[varName] ?? "") + leftOver + } + newTags.push(new Tag(key, actualValue)) + } else { + newTags.push(new Tag(key, value)) + } + } + return newTags + }) + + } + + public static HelpMessage() { const helpTexts = SpecialVisualizations.specialVisualizations.map(viz => new Combine( @@ -541,7 +649,13 @@ There are also some technicalities in your theme to keep in mind: new Title(viz.funcName, 3), viz.docs, viz.args.length > 0 ? new Table(["name", "default", "description"], - viz.args.map(arg => [arg.name, arg.defaultValue ?? "undefined", arg.doc]) + viz.args.map(arg => { + let defaultArg = arg.defaultValue ?? "_undefined_" + if (defaultArg == "") { + defaultArg = "_empty string_" + } + return [arg.name, defaultArg, arg.doc]; + }) ) : undefined, new Title("Example usage", 4), new FixedUiElement( @@ -552,12 +666,18 @@ There are also some technicalities in your theme to keep in mind: )); + const toc = new List( + SpecialVisualizations.specialVisualizations.map(viz => new Link(viz.funcName, "#" + viz.funcName)) + ) + return new Combine([ new Title("Special tag renderings", 3), "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.", - "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_fcs need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args", + "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args", + toc, ...helpTexts ] - ); + ).SetClass("flex flex-col"); } + } \ No newline at end of file diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 361540e65..076073f75 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -8,6 +8,7 @@ import {Utils} from "../Utils"; import {VariableUiElement} from "./Base/VariableUIElement"; import Combine from "./Base/Combine"; import BaseUIElement from "./BaseUIElement"; +import {DefaultGuiState} from "./DefaultGuiState"; export class SubstitutedTranslation extends VariableUiElement { @@ -49,7 +50,7 @@ export class SubstitutedTranslation extends VariableUiElement { } const viz = proto.special; try { - return viz.func.constr(State.state, tagsSource, proto.special.args).SetStyle(proto.special.style); + return viz.func.constr(State.state, tagsSource, proto.special.args, DefaultGuiState.state).SetStyle(proto.special.style); } catch (e) { console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e) return new FixedUiElement(`Could not generate special rendering for ${viz.func}(${viz.args.join(", ")}) ${e}`).SetStyle("alert") @@ -85,7 +86,9 @@ export class SubstitutedTranslation extends VariableUiElement { const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings); const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); if (argument.length > 0) { - const realArgs = argument.split(",").map(str => str.trim()); + const realArgs = argument.split(",").map(str => str.trim() + .replace(/&LPARENS/g, '(') + .replace(/&RPARENS/g, ')')); for (let i = 0; i < realArgs.length; i++) { if (args.length <= i) { args.push(realArgs[i]); diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index d9e9d6e2d..fa5c0da7b 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -25,6 +25,9 @@ export default class Translations { if (t === undefined || t === null) { return undefined; } + if(typeof t === "number"){ + t = ""+t + } if (typeof t === "string") { return new Translation({"*": t}, context); } diff --git a/Utils.ts b/Utils.ts index a732f1ab8..e0dbf2641 100644 --- a/Utils.ts +++ b/Utils.ts @@ -15,6 +15,18 @@ export class Utils { private static injectedDownloads = {} private static _download_cache = new Map, timestamp: number }>() + public static Special_visualizations_tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`. +This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature. + +If a value to substitute is undefined, empty string will be used instead. + +This supports multiple values, e.g. \`ref=$source:geometry:type/$source:geometry:ref\` + +Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with \`[a-zA-Z0-9_:]*\`). Sadly, delimiting with \`{}\` as these already mark the boundaries of the special rendering... + +Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript) + ` + static EncodeXmlValue(str) { if (typeof str !== "string") { str = "" + str @@ -165,8 +177,10 @@ export class Utils { return [a.substr(0, index), a.substr(index + sep.length)]; } - public static SubstituteKeys(txt: string, tags: any) { - + public static SubstituteKeys(txt: string | undefined, tags: any): string | undefined { + if (txt === undefined) { + return undefined + } const regex = /.*{([^}]*)}.*/ let match = txt.match(regex) @@ -176,7 +190,7 @@ export class Utils { txt = txt.replace("{" + key + "}", tags[key] ?? "") match = txt.match(regex) } - + return txt; } @@ -455,11 +469,11 @@ export class Utils { const now = new Date() const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000) const date = lastWeek.getFullYear() + "-" + Utils.TwoDigits(lastWeek.getMonth() + 1) + "-" + Utils.TwoDigits(lastWeek.getDate()) - let osmcha_link = `{"date__gte":[{"label":"${date}","value":"${date}"}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}]}` + let osmcha_link = `"date__gte":[{"label":"${date}","value":"${date}"}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}]` if (theme !== undefined) { - osmcha_link = osmcha_link + "," + `{"comment":[{"label":"#${theme}","value":"#${theme}"}]` + osmcha_link = osmcha_link + "," + `"comment":[{"label":"#${theme}","value":"#${theme}"}]` } - return "https://osmcha.org/?filters=" + encodeURIComponent(osmcha_link) + return "https://osmcha.org/?filters=" + encodeURIComponent("{"+osmcha_link+"}") } private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) { diff --git a/assets/layers/artwork/artwork.json b/assets/layers/artwork/artwork.json index b89f83df7..e535bab72 100644 --- a/assets/layers/artwork/artwork.json +++ b/assets/layers/artwork/artwork.json @@ -394,5 +394,24 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/artwork/artwork.svg" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#0000ff" + }, + "width": { + "render": "10" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/barrier/barrier.json b/assets/layers/barrier/barrier.json index afa8bcfaf..7df7101d4 100644 --- a/assets/layers/barrier/barrier.json +++ b/assets/layers/barrier/barrier.json @@ -323,5 +323,16 @@ }, "id": "Overlap (cyclebarrier)" } + ], + "mapRendering": [ + { + "icon": "./assets/layers/barrier/barrier.svg", + "location": [ + "point" + ] + }, + { + "width": "5" + } ] } \ No newline at end of file diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index ca08d1575..11587d5aa 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -644,5 +644,18 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/layers/bench/bench.svg" + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/bench_at_pt/bench_at_pt.json b/assets/layers/bench_at_pt/bench_at_pt.json index 29c759a63..bc15ac3ba 100644 --- a/assets/layers/bench_at_pt/bench_at_pt.json +++ b/assets/layers/bench_at_pt/bench_at_pt.json @@ -148,5 +148,26 @@ }, "color": { "render": "#00f" - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/benches/bench_public_transport.svg" + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/bicycle_library/bicycle_library.json b/assets/layers/bicycle_library/bicycle_library.json index 735a45990..27bee2fd3 100644 --- a/assets/layers/bicycle_library/bicycle_library.json +++ b/assets/layers/bicycle_library/bicycle_library.json @@ -269,13 +269,11 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" }, { "if": "service:bicycle:pump=yes", - "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg", - "badge": true + "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg" } ], "width": { @@ -287,5 +285,37 @@ "color": { "render": "#c00" }, - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "pin:#22ff55;./assets/layers/bicycle_library/bicycle_library.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + }, + { + "if": "service:bicycle:pump=yes", + "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg" + } + ], + "iconSize": { + "render": "50,50,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#c00" + }, + "width": { + "render": "1" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json index 6f069aacf..8cb34bda5 100644 --- a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json +++ b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json @@ -48,8 +48,7 @@ "operational_status=closed" ] }, - "then": "close:#c33", - "badge": true + "then": "close:#c33" } ], "iconSize": "50,50,bottom", @@ -276,5 +275,31 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "pin:#ffffff;./assets/layers/bicycle_tube_vending_machine/pinIcon.svg" + }, + "iconBadges": [ + { + "if": { + "or": [ + "operational_status=broken", + "operational_status=closed" + ] + }, + "then": "close:#c33" + } + ], + "iconSize": "50,50,bottom", + "location": [ + "point", + "centroid" + ] + }, + { + "color": "#6bc4f7" + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index c3b328beb..8c453ebad 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -365,5 +365,27 @@ ] } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_cafe/bike_cafe.svg" + }, + "iconSize": { + "render": "50,50,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#694E2D" + }, + "width": { + "render": "2" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_cleaning/bike_cleaning.json b/assets/layers/bike_cleaning/bike_cleaning.json index 9e39755c7..eb70066a4 100644 --- a/assets/layers/bike_cleaning/bike_cleaning.json +++ b/assets/layers/bike_cleaning/bike_cleaning.json @@ -79,13 +79,15 @@ ] }, "then": { - "render": "./assets/layers/bike_cleaning/bike_cleaning_icon.svg" + "render": "./assets/layers/bike_cleaning/bike_cleaning_icon.svg", + "roaming": true } } ], "titleIcons": [ { - "render": "" + "render": "", + "roaming": true } ], "tagRenderings": [ @@ -115,6 +117,7 @@ "then": "The cleaning service has a fee" } ], + "roaming": true, "id": "bike_cleaning-service:bicycle:cleaning:charge" }, { @@ -142,6 +145,7 @@ "then": "The cleaning service has a fee" } ], + "roaming": false, "id": "bike_cleaning-charge" } ], @@ -157,5 +161,30 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_cleaning/bike_cleaning.svg" + }, + "iconBadges": [ + { + "if": { + "and": [ + "service:bicycle:cleaning~*", + "amenity!=bike_wash" + ] + }, + "then": { + "render": "./assets/layers/bike_cleaning/bike_cleaning_icon.svg", + "roaming": true + } + } + ], + "iconSize": "50,50,bottom", + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_parking/bike_parking.json b/assets/layers/bike_parking/bike_parking.json index 380f97c37..574a06910 100644 --- a/assets/layers/bike_parking/bike_parking.json +++ b/assets/layers/bike_parking/bike_parking.json @@ -551,5 +551,21 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_parking/parking.svg" + }, + "iconSize": "40,40,bottom", + "location": [ + "point", + "centroid" + ] + }, + { + "color": "#00f", + "width": "1" + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index 6215ad1eb..bcbc81544 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -663,8 +663,7 @@ "iconOverlays": [ { "if": "operator=De Fietsambassade Gent", - "then": "./assets/themes/cyclofix/fietsambassade_gent_logo_small.svg", - "badge": true + "then": "./assets/themes/cyclofix/fietsambassade_gent_logo_small.svg" } ], "iconSize": { @@ -758,5 +757,76 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_repair_station/repair_station.svg", + "mappings": [ + { + "if": { + "and": [ + "service:bicycle:pump=no", + "service:bicycle:pump:operational_status=broken" + ] + }, + "then": "./assets/layers/bike_repair_station/repair_station.svg" + }, + { + "if": { + "and": [ + "service:bicycle:pump=yes", + "service:bicycle:tools=yes" + ] + }, + "then": "./assets/layers/bike_repair_station/repair_station_pump.svg" + }, + { + "if": { + "and": [ + "service:bicycle:pump:operational_status=broken", + "service:bicycle:tools=no" + ] + }, + "then": "./assets/layers/bike_repair_station/broken_pump_2.svg" + }, + { + "if": { + "and": [ + "service:bicycle:pump=yes", + { + "or": [ + "service:bicycle:tools=no", + "service:bicycle:tools=" + ] + } + ] + }, + "then": "./assets/layers/bike_repair_station/pump.svg" + } + ] + }, + "iconBadges": [ + { + "if": "operator=De Fietsambassade Gent", + "then": "./assets/themes/cyclofix/fietsambassade_gent_logo_small.svg" + } + ], + "iconSize": { + "render": "50,50,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "1" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index 6cd8890c3..991843e5f 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -752,5 +752,57 @@ "color": { "render": "#c00" }, - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_shop/repair_shop.svg", + "mappings": [ + { + "if": "operator=De Fietsambassade Gent", + "then": "./assets/themes/cyclofix/fietsambassade_gent_logo_small.svg" + }, + { + "if": "service:bicycle:retail=yes", + "then": "./assets/layers/bike_shop/shop.svg" + } + ] + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + }, + { + "if": "service:bicycle:pump=yes", + "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg" + }, + { + "if": { + "and": [ + "service:bicycle:cleaning~*" + ] + }, + "then": { + "render": "./assets/layers/bike_cleaning/bike_cleaning_icon.svg" + } + } + ], + "iconSize": { + "render": "50,50,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#c00" + }, + "width": { + "render": "1" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/bike_themed_object/bike_themed_object.json b/assets/layers/bike_themed_object/bike_themed_object.json index 527376742..0a7e4322b 100644 --- a/assets/layers/bike_themed_object/bike_themed_object.json +++ b/assets/layers/bike_themed_object/bike_themed_object.json @@ -69,5 +69,27 @@ "render": "#AB76D5" }, "presets": [], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/bike_themed_object/other_services.svg" + }, + "iconSize": { + "render": "50,50,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#AB76D5" + }, + "width": { + "render": "2" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/binocular/binocular.json b/assets/layers/binocular/binocular.json index 5b84365e2..6bbb7ca6a 100644 --- a/assets/layers/binocular/binocular.json +++ b/assets/layers/binocular/binocular.json @@ -127,5 +127,26 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/binocular/telescope.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/birdhide/birdhide.json b/assets/layers/birdhide/birdhide.json index c8015e5f4..c09c7e958 100644 --- a/assets/layers/birdhide/birdhide.json +++ b/assets/layers/birdhide/birdhide.json @@ -309,5 +309,29 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": { + "nl": "./assets/layers/birdhide/birdhide.svg" + }, + "mappings": [ + { + "if": { + "or": [ + "building=yes", + "shelter=yes", + "amenity=shelter" + ] + }, + "then": "./assets/layers/birdhide/birdshelter.svg" + } + ] + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index cd5f6de2e..dbd65532a 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -28,8 +28,7 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "label": { @@ -203,5 +202,35 @@ ] } }, - "allowMove": true + "allowMove": true, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/cafe_pub/pub.svg", + "mappings": [ + { + "if": "amenity=cafe", + "then": "circle:white;./assets/layers/cafe_pub/cafe.svg" + } + ] + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "label": { + "mappings": [ + { + "if": "name~*", + "then": "
{name}
" + } + ] + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 3ec94777e..22d87c33e 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -2,13 +2,7 @@ "id": "charging_station", "name": { "en": "Charging stations", - "nl": "Oplaadpunten", - "de": "Ladestationen", - "it": "Stazioni di ricarica", - "ja": "充電ステーション", - "nb_NO": "Ladestasjoner", - "ru": "Зарядные станции", - "zh_Hant": "充電站" + "nl": "Oplaadpunten" }, "minzoom": 10, "source": { @@ -24,24 +18,12 @@ "title": { "render": { "en": "Charging station", - "nl": "Oplaadpunten", - "de": "Ladestation", - "it": "Stazione di ricarica", - "ja": "充電ステーション", - "nb_NO": "Ladestasjon", - "ru": "Зарядная станция", - "zh_Hant": "充電站" + "nl": "Oplaadpunten" } }, "description": { "en": "A charging station", - "nl": "Oplaadpunten", - "de": "Eine Ladestation", - "it": "Una stazione di ricarica", - "ja": "充電ステーション", - "nb_NO": "En ladestasjon", - "ru": "Зарядная станция", - "zh_Hant": "充電站" + "nl": "Oplaadpunten" }, "tagRenderings": [ "images", @@ -1699,7 +1681,7 @@ "planned:amenity=", "construction:amenity=", "disused:amenity=", - "operational_status=", + "operational_status=broken", "amenity=charging_station" ] }, @@ -1712,11 +1694,11 @@ { "if": { "and": [ - "planned:amenity=", + "planned:amenity=charging_station", "construction:amenity=", "disused:amenity=", - "operational_status=broken", - "amenity=charging_station" + "operational_status=", + "amenity=" ] }, "then": { @@ -1728,8 +1710,8 @@ { "if": { "and": [ - "planned:amenity=charging_station", - "construction:amenity=", + "planned:amenity=", + "construction:amenity=charging_station", "disused:amenity=", "operational_status=", "amenity=" @@ -1745,8 +1727,8 @@ "if": { "and": [ "planned:amenity=", - "construction:amenity=charging_station", - "disused:amenity=", + "construction:amenity=", + "disused:amenity=charging_station", "operational_status=", "amenity=" ] @@ -1762,9 +1744,9 @@ "and": [ "planned:amenity=", "construction:amenity=", - "disused:amenity=charging_station", + "disused:amenity=", "operational_status=", - "amenity=" + "amenity=charging_station" ] }, "then": { @@ -1811,69 +1793,69 @@ } } ], - "icon": { - "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", - "mappings": [ - { - "if": "bicycle=yes", - "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" - }, - { - "if": { - "or": [ - "car=yes", - "motorcar=yes" - ] - }, - "then": "pin:#fff;./assets/themes/charging_stations/car.svg" - } - ] - }, - "iconOverlays": [ + "mapRendering": [ { - "if": { - "or": [ - "disused:amenity=charging_station", - "operational_status=broken" - ] - }, - "then": "cross_bottom_right:#c22;" - }, - { - "if": { - "or": [ - "proposed:amenity=charging_station", - "planned:amenity=charging_station" - ] - }, - "then": "./assets/layers/charging_station/under_construction.svg", - "badge": true - }, - { - "if": { - "and": [ - "bicycle=yes", + "location": [ + "point", + "centroid" + ], + "icon": { + "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", + "mappings": [ { - "or": [ - "motorcar=yes", - "car=yes" - ] + "if": "bicycle=yes", + "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" + }, + { + "if": { + "or": [ + "car=yes", + "motorcar=yes" + ] + }, + "then": "pin:#fff;./assets/themes/charging_stations/car.svg" } ] }, - "then": "circle:#fff;./assets/themes/charging_stations/car.svg", - "badge": true + "iconBadges": [ + { + "if": { + "or": [ + "disused:amenity=charging_station", + "operational_status=broken" + ] + }, + "then": "cross:#c22;" + }, + { + "if": { + "or": [ + "proposed:amenity=charging_station", + "planned:amenity=charging_station" + ] + }, + "then": "./assets/layers/charging_station/under_construction.svg" + }, + { + "if": { + "and": [ + "bicycle=yes", + { + "or": [ + "motorcar=yes", + "car=yes" + ] + } + ] + }, + "then": "circle:#fff;./assets/themes/charging_stations/car.svg" + } + ], + "iconSize": { + "render": "50,50,bottom" + } } ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "50,50,bottom" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ diff --git a/assets/layers/charging_station/charging_station.protojson b/assets/layers/charging_station/charging_station.protojson index ac7a86b9b..e670ad798 100644 --- a/assets/layers/charging_station/charging_station.protojson +++ b/assets/layers/charging_station/charging_station.protojson @@ -558,21 +558,6 @@ "nl": "Is dit oplaadpunt operationeel?" }, "mappings": [ - { - "if": { - "and": [ - "planned:amenity=", - "construction:amenity=", - "disused:amenity=", - "operational_status=", - "amenity=charging_station" - ] - }, - "then": { - "en": "This charging station works", - "nl": "Dit oplaadpunt werkt" - } - }, { "if": { "and": [ @@ -632,6 +617,21 @@ "en": "This charging station has beed permanently disabled and is not in use anymore but is still visible", "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig" } + }, + { + "if": { + "and": [ + "planned:amenity=", + "construction:amenity=", + "disused:amenity=", + "operational_status=", + "amenity=charging_station" + ] + }, + "then": { + "en": "This charging station works", + "nl": "Dit oplaadpunt werkt" + } } ] }, @@ -668,69 +668,69 @@ } } ], - "icon": { - "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", - "mappings": [ - { - "if": "bicycle=yes", - "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" - }, - { - "if": { - "or": [ - "car=yes", - "motorcar=yes" - ] - }, - "then": "pin:#fff;./assets/themes/charging_stations/car.svg" - } - ] - }, - "iconOverlays": [ + "mapRendering": [ { - "if": { - "or": [ - "disused:amenity=charging_station", - "operational_status=broken" - ] - }, - "then": "cross_bottom_right:#c22;" - }, - { - "if": { - "or": [ - "proposed:amenity=charging_station", - "planned:amenity=charging_station" - ] - }, - "then": "./assets/layers/charging_station/under_construction.svg", - "badge": true - }, - { - "if": { - "and": [ - "bicycle=yes", + "location": [ + "point", + "centroid" + ], + "icon": { + "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", + "mappings": [ { - "or": [ - "motorcar=yes", - "car=yes" - ] + "if": "bicycle=yes", + "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" + }, + { + "if": { + "or": [ + "car=yes", + "motorcar=yes" + ] + }, + "then": "pin:#fff;./assets/themes/charging_stations/car.svg" } ] }, - "then": "circle:#fff;./assets/themes/charging_stations/car.svg", - "badge": true + "iconBadges": [ + { + "if": { + "or": [ + "disused:amenity=charging_station", + "operational_status=broken" + ] + }, + "then": "cross:#c22;" + }, + { + "if": { + "or": [ + "proposed:amenity=charging_station", + "planned:amenity=charging_station" + ] + }, + "then": "./assets/layers/charging_station/under_construction.svg" + }, + { + "if": { + "and": [ + "bicycle=yes", + { + "or": [ + "motorcar=yes", + "car=yes" + ] + } + ] + }, + "then": "circle:#fff;./assets/themes/charging_stations/car.svg" + } + ], + "iconSize": { + "render": "50,50,bottom" + } } ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "50,50,bottom" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ diff --git a/assets/layers/cluster_style/cluster_style.json b/assets/layers/cluster_style/cluster_style.json index 7e93c506a..caaa33559 100644 --- a/assets/layers/cluster_style/cluster_style.json +++ b/assets/layers/cluster_style/cluster_style.json @@ -36,5 +36,43 @@ "then": "
{kilocount}K
" } ] - } + }, + "mapRendering": [ + { + "label": { + "render": "
{showCount}
", + "mappings": [ + { + "if": "showCount>1000", + "then": "
{kilocount}K
" + } + ] + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#3c3", + "mappings": [ + { + "if": "showCount>200", + "then": "#f33" + }, + { + "if": "showCount>100", + "then": "#c93" + }, + { + "if": "showCount>50", + "then": "#cc3" + } + ] + }, + "width": { + "render": "1" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/conflation/conflation.json b/assets/layers/conflation/conflation.json new file mode 100644 index 000000000..2f638cfa5 --- /dev/null +++ b/assets/layers/conflation/conflation.json @@ -0,0 +1,36 @@ +{ + "id": "conflation", + "description": "This is a special meta_layer which render geometry-changes for inspection", + "minzoom": 1, + "source": { + "osmTags": { + "or": [ + "move=yes", + "newpoint=yes" + ] + } + }, + "name": "Conflation", + "title": "Conflation", + "mapRendering": [ + { + "location": "point", + "icon": "addSmall:#000", + "iconSize": "10,10,center" + }, + { + "location": "end", + "icon": "circle:#0f0", + "iconSize": "10,10,center" + }, + { + "location": "start", + "icon": "square:#f00", + "iconSize": "10,10,center" + }, + { + "width": "3", + "color": "#00f" + } + ] +} \ No newline at end of file diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index b257f101c..b65f8aa2b 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -202,8 +202,7 @@ "id": "crossing-has-island", "question": { "en": "Does this crossing have an island in the middle?", - "nl": "Heeft deze oversteekplaats een verkeerseiland in het midden?", - "de": "Gibt es an diesem Übergang eine Verkehrsinsel?" + "nl": "Heeft deze oversteekplaats een verkeerseiland in het midden?" }, "condition": "highway=crossing", "mappings": [ @@ -211,16 +210,14 @@ "if": "crossing:island=yes", "then": { "en": "This crossing has an island in the middle", - "nl": "Deze oversteekplaats heeft een verkeerseiland in het midden", - "de": "Der Übergang hat eine Verkehrsinsel" + "nl": "Deze oversteekplaats heeft een verkeerseiland in het midden" } }, { "if": "crossing:island=no", "then": { "en": "This crossing does not have an island in the middle", - "nl": "Deze oversteekplaats heeft geen verkeerseiland in het midden", - "de": "Diese Ampel hat eine Taste, um ein grünes Signal anzufordern" + "nl": "Deze oversteekplaats heeft geen verkeerseiland in het midden" } } ] @@ -264,8 +261,7 @@ "id": "crossing-button", "question": { "en": "Does this traffic light have a button to request green light?", - "nl": "Heeft dit verkeerslicht een knop voor groen licht?", - "de": "Hat diese Ampel eine Taste, um ein grünes Signal anzufordern?" + "nl": "Heeft dit verkeerslicht een knop voor groen licht?" }, "condition": { "or": [ @@ -285,8 +281,7 @@ "if": "button_operated=no", "then": { "en": "This traffic light does not have a button to request green light", - "nl": "Dit verkeerlicht heeft geen knop voor groen licht", - "de": "Diese Ampel hat keine Taste, um ein grünes Signal anzufordern." + "nl": "Dit verkeerlicht heeft geen knop voor groen licht" } } ] @@ -295,8 +290,7 @@ "id": "crossing-right-turn-through-red", "question": { "en": "Can a cyclist turn right when the light is red?", - "nl": "Mag een fietser rechtsaf slaan als het licht rood is?", - "de": "Kann ein Radfahrer bei roter Ampel rechts abbiegen?" + "nl": "Mag een fietser rechtsaf slaan als het licht rood is?" }, "condition": "highway=traffic_signals", "mappings": [ @@ -304,8 +298,7 @@ "if": "red_turn:right:bicycle=yes", "then": { "en": "A cyclist can turn right if the light is red ", - "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is ", - "de": "Ein Radfahrer kann bei roter Ampel rechts abbiegen " + "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is " }, "hideInAnswer": "_country!=be" }, @@ -313,8 +306,7 @@ "if": "red_turn:right:bicycle=yes", "then": { "en": "A cyclist can turn right if the light is red", - "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is", - "de": "Ein Radfahrer kann bei roter Ampel rechts abbiegen" + "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is" }, "hideInAnswer": "_country=be" }, @@ -322,8 +314,7 @@ "if": "red_turn:right:bicycle=no", "then": { "en": "A cyclist can not turn right if the light is red", - "nl": "Een fietser mag niet rechtsaf slaan als het licht rood is", - "de": "Ein Radfahrer kann bei roter Ampel nicht rechts abbiegen" + "nl": "Een fietser mag niet rechtsaf slaan als het licht rood is" } } ] @@ -332,8 +323,7 @@ "id": "crossing-continue-through-red", "question": { "en": "Can a cyclist go straight on when the light is red?", - "nl": "Mag een fietser rechtdoor gaan als het licht rood is?", - "de": "Kann ein Radfahrer bei roter Ampel geradeaus fahren?" + "nl": "Mag een fietser rechtdoor gaan als het licht rood is?" }, "condition": "highway=traffic_signals", "mappings": [ @@ -341,8 +331,7 @@ "if": "red_turn:straight:bicycle=yes", "then": { "en": "A cyclist can go straight on if the light is red ", - "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is ", - "de": "Ein Radfahrer kann bei roter Ampel geradeaus fahren " + "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is " }, "hideInAnswer": "_country!=be" }, @@ -350,8 +339,7 @@ "if": "red_turn:straight:bicycle=yes", "then": { "en": "A cyclist can go straight on if the light is red", - "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is", - "de": "Ein Radfahrer kann bei roter Ampel geradeaus fahren" + "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is" }, "hideInAnswer": "_country=be" }, @@ -359,11 +347,34 @@ "if": "red_turn:straight:bicycle=no", "then": { "en": "A cyclist can not go straight on if the light is red", - "nl": "Een fietser mag niet rechtdoor gaan als het licht rood is", - "de": "Ein Radfahrer kann bei roter Ampel nicht geradeaus fahren" + "nl": "Een fietser mag niet rechtdoor gaan als het licht rood is" } } ] } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/crossings/pedestrian_crossing.svg", + "mappings": [ + { + "if": { + "or": [ + "highway=traffic_signals", + "crossing=traffic_signals" + ] + }, + "then": "./assets/layers/crossings/traffic_lights.svg" + } + ] + }, + "location": [ + "point" + ] + }, + { + "width": "5" + } ] } \ No newline at end of file diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index da3f5ba20..11f160c4e 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -83,7 +83,6 @@ } ] }, - "description": {}, "tagRenderings": [ { "question": { @@ -1277,5 +1276,87 @@ } ] }, - "allowSplit": true + "allowSplit": true, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/cycle_infra/bicycleway.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "rgba(170, 170, 170, 0.7)", + "mappings": [ + { + "if": "highway=cycleway", + "then": "rgba(0, 189, 141, 0.7)" + }, + { + "if": "highway=path", + "then": "rgba(204, 74, 207, 0.7)" + }, + { + "if": "cycleway=track", + "then": "rgba(113, 3, 200, 0.7)" + }, + { + "if": "cycleway=shared_lane", + "then": "rgba(74, 59, 247, 0.7)" + }, + { + "if": "cycleway=lane", + "then": "rgba(254, 155, 6, 0.9)" + }, + { + "if": "cyclestreet=yes", + "then": "rgba(57, 159, 191, 0.7)" + } + ] + }, + "width": { + "render": "8" + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": { + "or": [ + "oneway=yes", + { + "or": [ + "highway=cycleway", + "highway=path" + ] + } + ] + }, + "then": "" + }, + { + "if": "cycleway=track", + "then": "" + }, + { + "if": "cycleway=shared_lane", + "then": "15 30" + }, + { + "if": "cycleway=lane", + "then": "25 15 15 15 25" + }, + { + "if": "cyclestreet=yes", + "then": "" + } + ] + } + } + ] } \ No newline at end of file diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 105536678..8239166c2 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -35,7 +35,13 @@ "mappings": [ { "if": "_recently_surveyed=true", - "then": "./assets/layers/defibrillator/aed_checked.svg" + "then": { + "en": "./assets/layers/defibrillator/aed_checked.svg", + "ru": "./assets/layers/defibrillator/aed_checked.svg", + "it": "./assets/layers/defibrillator/aed_checked.svg", + "fr": "./assets/layers/defibrillator/aed_checked.svg", + "de": "./assets/layers/defibrillator/aed_checked.svg" + } } ] }, @@ -203,8 +209,7 @@ "en": "Is this a a regular automatic defibrillator or a manual defibrillator for professionals only?", "nl": "Is dit een gewone automatische defibrillator of een manueel toestel enkel voor professionals?", "fr": "Est-ce un défibrillateur automatique normal ou un défibrillateur manuel à usage professionnel uniquement ?", - "it": "Si tratta di un normale defibrillatore automatico o un defibrillatore manuale riservato ai professionisti?", - "de": "Ist dies ein normaler automatischer Defibrillator oder ein manueller Defibrillator nur für Profis?" + "it": "Si tratta di un normale defibrillatore automatico o un defibrillatore manuale riservato ai professionisti?" }, "freeform": { "key": "defibrillator" @@ -551,5 +556,24 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/aed/aed.svg", + "mappings": [ + { + "if": "_recently_surveyed=true", + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + ] + }, + "location": [ + "point" + ] + }, + { + "color": "#0000ff" + } + ] } \ No newline at end of file diff --git a/assets/layers/direction/direction.json b/assets/layers/direction/direction.json index 75cd375c2..2114615ff 100644 --- a/assets/layers/direction/direction.json +++ b/assets/layers/direction/direction.json @@ -45,5 +45,30 @@ "color": "--catch-detail-color", "stroke": "0", "presets": [], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "direction_gradient:var(--catch-detail-color)", + "#": "For some weird reason, showing the icon in the layer control panel breaks the svg-gradient (because the svg gradient has a global color or smthng) - so we use a different icon without gradient", + "mappings": [ + { + "if": "id=node/-1", + "then": "direction:var(--catch-detail-color)" + } + ] + }, + "iconSize": "200,200,center", + "location": [ + "point", + "centroid" + ], + "rotation": { + "render": "{_direction:numerical}deg" + } + }, + { + "color": "--catch-detail-color" + } + ] } \ No newline at end of file diff --git a/assets/layers/drinking_water/drinking_water.json b/assets/layers/drinking_water/drinking_water.json index 1170a823e..d2e705d9c 100644 --- a/assets/layers/drinking_water/drinking_water.json +++ b/assets/layers/drinking_water/drinking_water.json @@ -33,8 +33,7 @@ "operational_status=closed" ] }, - "then": "close:#c33", - "badge": true + "then": "close:#c33" } ], "iconSize": "40,40,bottom", @@ -61,7 +60,7 @@ "nl": "drinkbaar water", "fr": "eau potable", "gl": "auga potábel", - "de": "Trinkwasserstelle", + "de": "trinkwasser", "it": "acqua potabile", "ru": "питьевая вода", "id": "air minum" @@ -99,8 +98,7 @@ "en": "This drinking water works", "nl": "Deze drinkwaterfontein werkt", "it": "La fontanella funziona", - "fr": "Cette fontaine fonctionne", - "de": "Diese Trinkwasserstelle funktioniert" + "fr": "Cette fontaine fonctionne" } }, { @@ -164,7 +162,7 @@ "en": "There is another drinking water fountain at {_closest_other_drinking_water_distance} meter", "nl": "Er bevindt zich een ander drinkwaterpunt op {_closest_other_drinking_water_distance} meter", "it": "C’è un’altra fontanella a {_closest_other_drinking_water_distance} metri", - "de": "Eine weitere Trinkwasserstelle liegt {_closest_other_drinking_water_distance} Meter entfernt", + "de": "Ein weiterer Trinkwasserbrunnen befindet sich in {_closest_other_drinking_water_distance} Meter", "fr": "Une autre source d’eau potable est à {_closest_other_drinking_water_distance} mètres a>" }, "condition": "_closest_other_drinking_water_id~*" @@ -182,5 +180,27 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "pin:#6BC4F7;./assets/layers/drinking_water/drips.svg" + }, + "iconBadges": [ + { + "if": { + "or": [ + "operational_status=broken", + "operational_status=closed" + ] + }, + "then": "close:#c33" + } + ], + "iconSize": "40,40,bottom", + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json index eaefef94b..f914b9432 100644 --- a/assets/layers/etymology/etymology.json +++ b/assets/layers/etymology/etymology.json @@ -38,8 +38,7 @@ "id": "wikipedia-etymology", "question": { "en": "What is the Wikidata-item that this object is named after?", - "nl": "Wat is het Wikidata-item van hetgeen dit object is naar vernoemd?", - "de": "Was ist das Wikidata-Element, nach dem dieses Objekt benannt ist?" + "nl": "Wat is het Wikidata-item van hetgeen dit object is naar vernoemd?" }, "freeform": { "key": "name:etymology:wikidata", @@ -72,8 +71,7 @@ }, "render": { "en": "

Wikipedia article of the name giver

{wikipedia(name:etymology:wikidata):max-height:20rem}", - "nl": "

Wikipedia artikel van de naamgever

{wikipedia(name:etymology:wikidata):max-height:20rem}", - "de": "

Wikipedia Artikel zur Namensherkunft

{wikipedia(name:etymology:wikidata):max-height:20rem}" + "nl": "

Wikipedia artikel van de naamgever

{wikipedia(name:etymology:wikidata):max-height:20rem}" }, "condition": "name:etymology!=unknown" }, @@ -89,8 +87,7 @@ "id": "simple etymology", "question": { "en": "What is this object named after?
This might be written on the street name sign", - "nl": "Naar wat is dit object vernoemd?
Dit staat mogelijks vermeld op het straatnaambordje", - "de": "Wonach ist dieses Objekt benannt?
Das könnte auf einem Straßenschild stehen" + "nl": "Naar wat is dit object vernoemd?
Dit staat mogelijks vermeld op het straatnaambordje" }, "render": { "en": "Named after {name:etymology}", @@ -105,8 +102,7 @@ "if": "name:etymology=unknown", "then": { "en": "The origin of this name is unknown in all literature", - "nl": "De oorsprong van deze naam is onbekend in de literatuur", - "de": "Der Ursprung dieses Namens ist in der gesamten Literatur unbekannt" + "nl": "De oorsprong van deze naam is onbekend in de literatuur" } } ], @@ -171,5 +167,48 @@ "then": "#fcca05aa" } ] - } + }, + "mapRendering": [ + { + "icon": { + "render": "pin:#05d7fcaa;./assets/layers/etymology/logo.svg", + "mappings": [ + { + "if": { + "and": [ + "name:etymology=", + "name:etymology:wikidata=" + ] + }, + "then": "pin:#fcca05aa;./assets/layers/etymology/logo.svg" + } + ] + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#05d7fcaa", + "mappings": [ + { + "if": { + "and": [ + "name:etymology=", + "name:etymology:wikidata=" + ] + }, + "then": "#fcca05aa" + } + ] + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 44ce21fb1..5e0e37c6d 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -36,8 +36,7 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" }, { "if": { @@ -48,8 +47,7 @@ }, "then": { "render": "circle:white;./assets/themes/fritures/Vegetarian-mark.svg" - }, - "badge": true + } } ], "label": { @@ -73,8 +71,7 @@ ], "description": { "nl": "Een eetgegelegenheid waar je aan tafel wordt bediend", - "en": "A formal eating place with sit-down facilities selling full meals served by waiters", - "de": "Ein klassisches Speiselokal mit Sitzgelegenheiten, in dem vollständige Mahlzeiten von Kellnern serviert werden" + "en": "A formal eating place with sit-down facilities selling full meals served by waiters" }, "preciseInput": { "preferredBackground": "map" @@ -217,16 +214,14 @@ "if": "cuisine=pizza", "then": { "en": "This is a pizzeria", - "nl": "Dit is een pizzeria", - "de": "Dies ist eine Pizzeria" + "nl": "Dit is een pizzeria" } }, { "if": "cuisine=friture", "then": { "en": "This is a friture", - "nl": "Dit is een frituur", - "de": "Dies ist eine Pommesbude" + "nl": "Dit is een frituur" } }, { @@ -349,8 +344,7 @@ { "question": { "nl": "Heeft deze eetgelegenheid een vegetarische optie?", - "en": "Does this restaurant have a vegetarian option?", - "de": "Gibt es im das Restaurant vegetarische Speisen?" + "en": "Does this restaurant have a vegetarian option?" }, "mappings": [ { @@ -417,8 +411,7 @@ { "question": { "en": "Does this restaurant offer a halal menu?", - "nl": "Heeft dit restaurant halal opties?", - "de": "Gibt es im das Restaurant halal Speisen?" + "nl": "Heeft dit restaurant halal opties?" }, "mappings": [ { @@ -549,8 +542,7 @@ "nl": "Als je je eigen container (bv. kookpot of kleine potjes voor saus) meeneemt, gebruikt de frituur deze dan om je bestelling in te doen?", "fr": "Est-il proposé d’utiliser ses propres contenants pour sa commande ?
", "en": "If you bring your own container (such as a cooking pot and small pots), is it used to package your order?
", - "ja": "お客様が持参容器(調理用の鍋や小さな鍋など)をもってきた場合は、注文の梱包に使用されますか?
", - "de": "Wenn Sie Ihr eigenes Behältnis mitbringen (z. B. einen Kochtopf und kleine Töpfe), wird es dann zum Verpacken Ihrer Bestellung verwendet?
" + "ja": "お客様が持参容器(調理用の鍋や小さな鍋など)をもってきた場合は、注文の梱包に使用されますか?
" }, "mappings": [ { @@ -559,8 +551,7 @@ "nl": "Je mag je eigen containers meenemen om je bestelling in mee te nemen en zo minder afval te maken", "fr": "Vous pouvez apporter vos contenants pour votre commande, limitant l’usage de matériaux à usage unique et les déchets", "en": "You can bring your own containers to get your order, saving on single-use packaging material and thus waste", - "ja": "自分の容器を持ってきて、注文を受け取ることができ、使い捨ての梱包材を節約して、無駄を省くことができます", - "de": "Sie können ihre eigenen Behälter mitbringen, um Ihre Bestellung zu erhalten, was Einwegverpackungsmaterial und damit Abfall spart" + "ja": "自分の容器を持ってきて、注文を受け取ることができ、使い捨ての梱包材を節約して、無駄を省くことができます" } }, { @@ -607,8 +598,7 @@ { "question": { "en": "Has a vegetarian menu", - "nl": "Heeft een vegetarisch menu", - "de": "Hat vegetarische Speisen" + "nl": "Heeft een vegetarisch menu" }, "osmTags": { "or": [ @@ -645,8 +635,7 @@ { "question": { "en": "Has a halal menu", - "nl": "Heeft een halal menu", - "de": "Hat halal Speisen" + "nl": "Heeft een halal menu" }, "osmTags": { "or": [ @@ -666,5 +655,55 @@ ] } }, - "allowMove": true + "allowMove": true, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/food/restaurant.svg", + "mappings": [ + { + "if": { + "and": [ + "amenity=fast_food", + "cuisine=friture" + ] + }, + "then": "circle:white;./assets/layers/food/fries.svg" + }, + { + "if": "amenity=fast_food", + "then": "circle:white;./assets/layers/food/fastfood.svg" + } + ] + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + }, + { + "if": { + "or": [ + "diet:vegetarian=yes", + "diet:vegan=yes" + ] + }, + "then": { + "render": "circle:white;./assets/themes/fritures/Vegetarian-mark.svg" + } + } + ], + "label": { + "mappings": [ + { + "if": "name~*", + "then": "
{name}
" + } + ] + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/ghost_bike/ghost_bike.json b/assets/layers/ghost_bike/ghost_bike.json index b3fb46860..0e8e7ac46 100644 --- a/assets/layers/ghost_bike/ghost_bike.json +++ b/assets/layers/ghost_bike/ghost_bike.json @@ -84,7 +84,7 @@ "render": { "en": "A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.", "nl": "Een Witte Fiets (of Spookfiets) is een aandenken aan een fietser die bij een verkeersongeval om het leven kwam. Het gaat over een witgeschilderde fiets die geplaatst werd in de buurt van het ongeval.", - "de": "Ein Geisterrad ist ein weißes Fahrrad, dass zum Gedenken eines tödlich verunglückten Radfahrers vor Ort aufgestellt wurde.", + "de": "Ein Geisterrad ist ein Denkmal für einen Radfahrer, der bei einem Verkehrsunfall ums Leben kam, in Form eines weißen Fahrrades, das dauerhaft in der Nähe des Unfallortes aufgestellt wird.", "it": "Una bici fantasma è il memoriale di un ciclista che è morto in un incidente stradale e che ha la forma di una bicicletta bianca piazzata in maniera stabile vicino al luogo dell’incidente.", "fr": "Un vélo fantôme est un monument commémoratif pour un cycliste décédé dans un accident de la route, sous la forme d'un vélo blanc placé en permanence près du lieu de l'accident." } @@ -182,8 +182,7 @@ "en": "Placed on {start_date}", "it": "Piazzata in data {start_date}", "fr": "Placé le {start_date}", - "ru": "Установлен {start_date}", - "de": "Aufgestellt am {start_date}" + "ru": "Установлен {start_date}" }, "freeform": { "key": "start_date", @@ -204,5 +203,14 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": "./assets/layers/ghost_bike/ghost_bike.svg", + "iconSize": "40,40,bottom", + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/gps_location/gps_location.json b/assets/layers/gps_location/gps_location.json new file mode 100644 index 000000000..617349405 --- /dev/null +++ b/assets/layers/gps_location/gps_location.json @@ -0,0 +1,15 @@ +{ + "id": "gps_location", + "description": "Meta layer showing the current location of the user", + "minzoom": 0, + "source": { + "osmTags": "user:location=yes" + }, + "mapRendering": [ + { + "icon": "crosshair:#00f", + "iconSize": "40,40,center", + "location": "point" + } + ] +} \ No newline at end of file diff --git a/assets/layers/grass_in_parks/grass_in_parks.json b/assets/layers/grass_in_parks/grass_in_parks.json index a95d6e0eb..e95a104b1 100644 --- a/assets/layers/grass_in_parks/grass_in_parks.json +++ b/assets/layers/grass_in_parks/grass_in_parks.json @@ -51,5 +51,19 @@ "id": "grass-in-parks-reviews", "render": "{reviews(name, landuse=grass )}" } + ], + "mapRendering": [ + { + "icon": "./assets/themes/playgrounds/playground.svg", + "iconSize": "40,40,center", + "location": [ + "point", + "centroid" + ] + }, + { + "color": "#0f0", + "width": "1" + } ] } \ No newline at end of file diff --git a/assets/layers/home_location/home_location.json b/assets/layers/home_location/home_location.json index 0bf07c75a..0955af79b 100644 --- a/assets/layers/home_location/home_location.json +++ b/assets/layers/home_location/home_location.json @@ -5,13 +5,15 @@ "source": { "osmTags": "user:home=yes" }, - "icon": { - "render": "circle:white;./assets/svg/home.svg" - }, - "iconSize": { - "render": "20,20,center" - }, - "color": { - "render": "#00f" - } + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/svg/home.svg" + }, + "iconSize": { + "render": "20,20,center" + }, + "location": "point" + } + ] } \ No newline at end of file diff --git a/assets/layers/information_board/information_board.json b/assets/layers/information_board/information_board.json index 1f6b08c2d..630461e50 100644 --- a/assets/layers/information_board/information_board.json +++ b/assets/layers/information_board/information_board.json @@ -68,5 +68,23 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/information_board/board.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/left_right_style/left_right_style.json b/assets/layers/left_right_style/left_right_style.json new file mode 100644 index 000000000..91fd76b79 --- /dev/null +++ b/assets/layers/left_right_style/left_right_style.json @@ -0,0 +1,35 @@ +{ + "id": "left_right_style", + "description": "Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads", + "source": { + "osmTags": { + "or": [ + "id=left", + "id=right" + ] + } + }, + "mapRendering": [ + { + "width": 15, + "color": { + "render": "#ff000088", + "mappings": [ + { + "if": "id=left", + "then": "#0000ff88" + } + ] + }, + "offset": { + "render": "-15", + "mappings": [ + { + "if": "id=right", + "then": "15" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/assets/layers/map/map.json b/assets/layers/map/map.json index 52083262b..dbf28faf7 100644 --- a/assets/layers/map/map.json +++ b/assets/layers/map/map.json @@ -241,5 +241,55 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/map/map.svg", + "mappings": [ + { + "if": { + "and": [ + "map_source=OpenStreetMap", + "map_source:attribution=sticker" + ] + }, + "then": "./assets/layers/map/map-stickered.svg" + }, + { + "if": { + "and": [ + "map_source=OpenStreetMap", + "map_source:attribution=yes" + ] + }, + "then": "./assets/layers/map/osm-logo-white-bg.svg" + }, + { + "if": { + "and": [ + "map_source=OpenStreetMap" + ] + }, + "then": "./assets/layers/map/osm-logo-buggy-attr.svg" + } + ] + }, + "iconSize": { + "render": "50,50,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/nature_reserve/nature_reserve.json b/assets/layers/nature_reserve/nature_reserve.json index 5dfb6ef0b..051cf6c4b 100644 --- a/assets/layers/nature_reserve/nature_reserve.json +++ b/assets/layers/nature_reserve/nature_reserve.json @@ -297,8 +297,7 @@ "nl": "Wie is de conservator van dit gebied?
Respecteer privacy - geef deze naam enkel als die duidelijk is gepubliceerd", "en": "Whom is the curator of this nature reserve?
Respect privacy - only fill out a name if this is widely published", "it": "Chi è il curatore di questa riserva naturale?
Rispetta la privacy (scrivi il nome solo se questo è noto pubblicamente)", - "fr": "Qui est en charge de la conservation de la réserve ?
À ne remplir seulement que si le nom est diffusé au public", - "de": "Wer ist der Verwalter dieses Naturschutzgebietes?
Respektieren Sie die Privatsphäre - geben Sie nur dann einen Namen an, wenn dieser allgemein bekannt ist" + "fr": "Qui est en charge de la conservation de la réserve ?
À ne remplir seulement que si le nom est diffusé au public" }, "render": { "nl": "{curator} is de beheerder van dit gebied", @@ -382,8 +381,7 @@ "en": "Surface area: {_surface:ha}Ha", "nl": "Totale oppervlakte: {_surface:ha}Ha", "it": "Area: {_surface:ha} ha", - "fr": "Superficie : {_surface:ha} ha", - "de": "Grundfläche: {_surface:ha}ha" + "fr": "Superficie : {_surface:ha} ha" }, "mappings": [ { @@ -463,5 +461,27 @@ } ] } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/nature_reserve/nature_reserve.svg" + }, + "iconSize": { + "render": "50,50,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#3c3" + }, + "width": { + "render": "1" + } + } ] } \ No newline at end of file diff --git a/assets/layers/observation_tower/observation_tower.json b/assets/layers/observation_tower/observation_tower.json index ab05bd66d..62dbe57c5 100644 --- a/assets/layers/observation_tower/observation_tower.json +++ b/assets/layers/observation_tower/observation_tower.json @@ -96,8 +96,7 @@ { "question": { "en": "How much does one have to pay to enter this tower?", - "nl": "Hoeveel moet men betalen om deze toren te bezoeken?", - "de": "Was kostet der Zugang zu diesem Turm?" + "nl": "Hoeveel moet men betalen om deze toren te bezoeken?" }, "render": { "en": "Visiting this tower costs {charge}", @@ -205,5 +204,18 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/observation_tower/Tower_observation.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index b428d5f99..b2eccdd9e 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -94,5 +94,18 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/parking/parking.svg" + }, + "iconSize": { + "render": "36,36,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index d98331acc..aec396493 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -115,5 +115,18 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:#e6cf39;./assets/layers/picnic_table/picnic_table.svg" + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/play_forest/play_forest.json b/assets/layers/play_forest/play_forest.json index 0b1f2485e..4244e81f5 100644 --- a/assets/layers/play_forest/play_forest.json +++ b/assets/layers/play_forest/play_forest.json @@ -116,5 +116,27 @@ "description": "Een zone in het bos, duidelijk gemarkeerd als speelzone met de overeenkomstige borden.
" } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/play_forest/icon.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#007055" + }, + "width": { + "render": "2" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/playground/playground.json b/assets/layers/playground/playground.json index 5a80bbdef..f4dfc4f3e 100644 --- a/assets/layers/playground/playground.json +++ b/assets/layers/playground/playground.json @@ -213,8 +213,7 @@ "en": "What is the minimum age required to access this playground?", "it": "Qual è l’età minima per accedere a questo parco giochi?", "fr": "Quel est l'âge minimal requis pour accéder à ce terrain de jeux ?", - "ru": "С какого возраста доступна эта детская площадка?", - "de": "Ab welchem Alter dürfen Kinder auf diesem Spielplatz spielen?" + "ru": "С какого возраста доступна эта детская площадка?" }, "freeform": { "key": "min_age", @@ -235,8 +234,7 @@ "nl": "Wat is de maximaal toegestane leeftijd voor deze speeltuin?", "en": "What is the maximum age allowed to access this playground?", "it": "Qual è l’età massima per accedere a questo parco giochi?", - "fr": "Quel est l’âge maximum autorisé pour utiliser l’aire de jeu ?", - "de": "Bis zu welchem Alter dürfen Kinder auf diesem Spielplatz spielen?" + "fr": "Quel est l’âge maximum autorisé pour utiliser l’aire de jeu ?" }, "freeform": { "key": "max_age", @@ -357,8 +355,7 @@ "nl": "Wie kan men bellen indien er problemen zijn met de speeltuin?", "en": "What is the phone number of the playground maintainer?", "fr": "Quel est le numéro de téléphone du responsable du terrain de jeux ?", - "it": "Qual è il numero di telefono del gestore del campetto?", - "de": "Wie lautet die Telefonnummer vom Betreiber des Spielplatzes?" + "it": "Qual è il numero di telefono del gestore del campetto?" }, "render": { "nl": "De bevoegde dienst kan getelefoneerd worden via
{phone}", @@ -491,8 +488,7 @@ "opening_hours~*" ] }, - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "width": { @@ -545,5 +541,56 @@ "leisure=" ] } - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/playgrounds/playground.svg" + }, + "iconBadges": [ + { + "if": { + "and": [ + "opening_hours!=24/7", + "opening_hours~*" + ] + }, + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,center", + "mappings": [ + { + "if": "id~node/.*", + "then": "40,40,center" + }, + { + "if": "_size_classification=small", + "then": "25,25,center" + }, + { + "if": "_size_classification=medium", + "then": "40,40,center" + }, + { + "if": "_size_classification=large", + "then": "60,60,center" + } + ] + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#5dbaa9" + }, + "width": { + "render": "1" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 407751fd9..5188b070a 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -45,7 +45,7 @@ ] }, "icon": { - "render": "./assets/themes/bookcases/bookcase.svg;" + "render": "./assets/themes/bookcases/bookcase.svg" }, "label": { "mappings": [ @@ -486,5 +486,33 @@ } ] } - ] + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/bookcases/bookcase.svg" + }, + "label": { + "mappings": [ + { + "if": "name~*", + "then": "
{name}
" + } + ] + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#0000ff" + }, + "width": { + "render": "8" + } + } + ], + "allowMove": true } \ No newline at end of file diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index e13f81877..eaa2c73fe 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -72,8 +72,7 @@ "fr": "Qu'est-ce que le nom de ce magasin?", "ru": "Как называется этот магазин?", "ja": "このお店の名前は何ですか?", - "nl": "Wat is de naam van deze winkel?", - "de": "Wie ist der Name dieses Geschäfts?" + "nl": "Wat is de naam van deze winkel?" }, "render": "This shop is called {name}", "freeform": { @@ -215,8 +214,7 @@ "fr": "Quel est le numéro de téléphone ?", "ja": "電話番号は何番ですか?", "nl": "Wat is het telefoonnummer?", - "ru": "Какой телефон?", - "de": "Wie ist die Telefonnummer?" + "ru": "Какой телефон?" }, "freeform": { "key": "phone", @@ -261,8 +259,7 @@ "fr": "Quelle est l'adresse électronique de ce magasin ?", "ja": "このお店のメールアドレスは何ですか?", "ru": "Каков адрес электронной почты этого магазина?", - "nl": "Wat is het e-mailadres van deze winkel?", - "de": "Wie ist die Email-Adresse dieses Geschäfts?" + "nl": "Wat is het e-mailadres van deze winkel?" }, "freeform": { "key": "email", @@ -282,8 +279,7 @@ "fr": "Quels sont les horaires d'ouverture de ce magasin ?", "ja": "この店の営業時間は何時から何時までですか?", "nl": "Wat zijn de openingsuren van deze winkel?", - "ru": "Каковы часы работы этого магазина?", - "de": "Wie sind die Öffnungszeiten dieses Geschäfts?" + "ru": "Каковы часы работы этого магазина?" }, "freeform": { "key": "opening_hours", @@ -300,8 +296,7 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "width": { @@ -323,8 +318,7 @@ "fr": "Magasin", "ru": "Магазин", "ja": "店", - "nl": "Winkel", - "de": "Geschäft" + "nl": "Winkel" }, "description": { "en": "Add a new shop", @@ -345,5 +339,33 @@ ] } }, - "allowMove": true + "allowMove": true, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/shops/shop.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/slow_roads/slow_roads.json b/assets/layers/slow_roads/slow_roads.json index 7f559bf2c..e1c344bde 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -116,8 +116,7 @@ "en": "The surface is {surface}", "ru": "Поверхность - {surface}", "fr": "La surface en {surface}", - "it": "La superficie è {surface}", - "de": "Die Oberfläche ist {surface}" + "it": "La superficie è {surface}" }, "freeform": { "key": "surface" @@ -130,8 +129,7 @@ "en": "The surface is grass", "ru": "Поверхность - трава", "fr": "La surface est en herbe", - "it": "La superficie è erba", - "de": "Die Oberfläche ist Gras" + "it": "La superficie è erba" } }, { @@ -141,8 +139,7 @@ "en": "The surface is ground", "ru": "Поверхность - земля", "fr": "La surface est en terre", - "it": "La superficie è terreno", - "de": "Die Oberfläche ist Erde" + "it": "La superficie è terreno" } }, { @@ -151,8 +148,7 @@ "nl": "De ondergrond is onverhard", "en": "The surface is unpaved", "fr": "La surface est non pavée", - "it": "La superficie è non pavimentata", - "de": "Die Oberfläche ist ohne festen Belag" + "it": "La superficie è non pavimentata" }, "hideInAnswer": true }, @@ -163,8 +159,7 @@ "en": "The surface is sand", "ru": "Поверхность - песок", "fr": "La surface est en sable", - "it": "La superficie è sabbia", - "de": "Die Oberfläche ist Sand" + "it": "La superficie è sabbia" } }, { @@ -174,8 +169,7 @@ "en": "The surface is paving stones", "ru": "Поверхность - брусчатка", "it": "La superficie è pietre irregolari", - "fr": "La surface est en pierres pavées", - "de": "Die Oberfläche ist aus Pflastersteinen" + "fr": "La surface est en pierres pavées" } }, { @@ -185,8 +179,7 @@ "en": "The surface is asphalt", "ru": "Поверхность - асфальт", "it": "La superficie è asfalto", - "fr": "La surface est en bitume", - "de": "Die Oberfläche ist Asphalt" + "fr": "La surface est en bitume" } }, { @@ -196,8 +189,7 @@ "en": "The surface is concrete", "ru": "Поверхность - бетон", "fr": "La surface est en béton", - "it": "La superficie è calcestruzzo", - "de": "Die Oberfläche ist Beton" + "it": "La superficie è calcestruzzo" } }, { @@ -206,8 +198,7 @@ "nl": "De ondergrond is verhard", "en": "The surface is paved", "fr": "La surface est pavée", - "it": "La superficie è pavimentata", - "de": "Die Oberfläche ist gepflastert" + "it": "La superficie è pavimentata" }, "hideInAnswer": true } @@ -261,5 +252,47 @@ "color": { "render": "#eaba2a" }, - "presets": [] + "presets": [], + "mapRendering": [ + { + "icon": "./assets/layers/slow_roads/slow_road.svg", + "location": [ + "point" + ] + }, + { + "color": { + "render": "#eaba2a" + }, + "width": { + "render": "7" + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": "highway=cycleway", + "then": "" + }, + { + "if": "highway=path", + "then": "0 12" + }, + { + "if": { + "or": [ + "highway=footway", + "highway=pedestrian" + ] + }, + "then": "12 18" + }, + { + "if": "highway=living_street", + "then": "12 12 0 12" + } + ] + } + } + ] } \ No newline at end of file diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json index 9a4f8c92c..d5648444f 100644 --- a/assets/layers/sport_pitch/sport_pitch.json +++ b/assets/layers/sport_pitch/sport_pitch.json @@ -160,8 +160,7 @@ "fr": "De quelle surface est fait ce terrain de sport ?", "en": "Which is the surface of this sport pitch?", "it": "Qual è la superficie di questo campo sportivo?", - "ru": "Какое покрытие на этой спортивной площадке?", - "de": "Was ist die Oberfläche dieses Sportplatzes?" + "ru": "Какое покрытие на этой спортивной площадке?" }, "render": { "nl": "De ondergrond is {surface}", @@ -262,8 +261,7 @@ "fr": "Accès limité (par exemple uniquement sur réservation, à certains horaires…)", "en": "Limited access (e.g. only with an appointment, during certain hours, ...)", "it": "Accesso limitato (p.es. solo con prenotazione, in certi orari, ...)", - "ru": "Ограниченный доступ (напр., только по записи, в определённые часы, ...)", - "de": "Eingeschränkter Zugang (z. B. nur mit Termin, zu bestimmten Zeiten, ...)" + "ru": "Ограниченный доступ (напр., только по записи, в определённые часы, ...)" } }, { @@ -296,8 +294,7 @@ "fr": "Doit-on réserver pour utiliser ce terrain de sport ?", "en": "Does one have to make an appointment to use this sport pitch?", "it": "È necessario prenotarsi per usare questo campo sportivo?", - "ru": "Нужна ли предварительная запись для доступа на эту спортивную площадку?", - "de": "Muss man einen Termin vereinbaren, um diesen Sportplatz zu benutzen?" + "ru": "Нужна ли предварительная запись для доступа на эту спортивную площадку?" }, "condition": { "and": [ @@ -313,8 +310,7 @@ "nl": "Reserveren is verplicht om gebruik te maken van dit sportterrein", "fr": "Il est obligatoire de réserver pour utiliser ce terrain de sport", "en": "Making an appointment is obligatory to use this sport pitch", - "it": "La prenotazione è obbligatoria per usare questo campo sportivo", - "de": "Für die Nutzung des Sportplatzes ist eine Voranmeldung erforderlich" + "it": "La prenotazione è obbligatoria per usare questo campo sportivo" } }, { @@ -324,8 +320,7 @@ "fr": "Il est recommendé de réserver pour utiliser ce terrain de sport", "en": "Making an appointment is recommended when using this sport pitch", "it": "La prenotazione è consigliata per usare questo campo sportivo", - "ru": "Желательна предварительная запись для доступа на эту спортивную площадку", - "de": "Für die Nutzung des Sportplatzes wird eine Voranmeldung empfohlen" + "ru": "Желательна предварительная запись для доступа на эту спортивную площадку" } }, { @@ -335,8 +330,7 @@ "fr": "Il est possible de réserver, mais ce n'est pas nécéssaire pour utiliser ce terrain de sport", "en": "Making an appointment is possible, but not necessary to use this sport pitch", "it": "La prenotazione è consentita, ma non è obbligatoria per usare questo campo sportivo", - "ru": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна", - "de": "Eine Voranmeldung ist möglich, aber nicht notwendig, um diesen Sportplatz zu nutzen" + "ru": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна" } }, { @@ -357,8 +351,7 @@ "nl": "Wat is het telefoonnummer van de bevoegde dienst of uitbater?", "fr": "Quel est le numéro de téléphone du gérant ?", "en": "What is the phone number of the operator?", - "it": "Qual è il numero di telefono del gestore?", - "de": "Wie ist die Telefonnummer des Betreibers?" + "it": "Qual è il numero di telefono del gestore?" }, "freeform": { "key": "phone", @@ -372,8 +365,7 @@ "nl": "Wat is het email-adres van de bevoegde dienst of uitbater?", "fr": "Quelle est l'adresse courriel du gérant ?", "en": "What is the email address of the operator?", - "it": "Qual è l'indirizzo email del gestore?", - "de": "Wie ist die Email-Adresse des Betreibers?" + "it": "Qual è l'indirizzo email del gestore?" }, "freeform": { "key": "email", @@ -452,8 +444,7 @@ "opening_hours~*" ] }, - "then": "isOpen", - "badge": true + "then": "isOpen" }, { "if": { @@ -463,8 +454,7 @@ "access=no" ] }, - "then": "circle:white;./assets/layers/sport_pitch/lock.svg", - "badge": true + "then": "circle:white;./assets/layers/sport_pitch/lock.svg" } ], "width": { @@ -524,5 +514,76 @@ "fixme=Toegevoegd met MapComplete, geometry nog uit te tekenen" ] } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/sport_pitch/sport_pitch.svg", + "mappings": [ + { + "if": { + "or": [ + "sport=baseball", + "sport=basketball", + "sport=beachvolleyball", + "sport=boules", + "sport=skateboard", + "sport=soccer", + "sport=table_tennis", + "sport=tennis", + "sport=volleyball" + ] + }, + "then": "circle:white;./assets/layers/sport_pitch/{sport}.svg" + } + ] + }, + "iconBadges": [ + { + "if": { + "and": [ + "opening_hours!=24/7", + "opening_hours~*" + ] + }, + "then": "isOpen" + }, + { + "if": { + "or": [ + "access=customers", + "access=private", + "access=no" + ] + }, + "then": "circle:white;./assets/layers/sport_pitch/lock.svg" + } + ], + "iconSize": { + "render": "25,25,center", + "mappings": [ + { + "if": { + "or": [ + "_size_classification=medium", + "id~node/.*" + ] + }, + "then": "40,40,center" + }, + { + "if": "_size_classification=small", + "then": "25,25,center" + }, + { + "if": "_size_classification=large", + "then": "50,50,center" + } + ] + }, + "location": [ + "point" + ] + } ] } \ No newline at end of file diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 44be895a1..0f4a1a3d1 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -97,8 +97,7 @@ "en": "In which geographical direction does this camera film?", "nl": "In welke geografische richting filmt deze camera?", "fr": "Dans quelle direction géographique cette caméra filme-t-elle ?", - "it": "In quale direzione geografica punta questa videocamera?", - "de": "In welche Himmelsrichtung ist diese Kamera ausgerichtet?" + "it": "In quale direzione geografica punta questa videocamera?" }, "render": { "en": "Films to a compass heading of {camera:direction}", @@ -167,8 +166,7 @@ "en": "What kind of surveillance is this camera", "nl": "Wat soort bewaking wordt hier uitgevoerd?", "fr": "Quel genre de surveillance est cette caméra", - "it": "Che cosa sorveglia questa videocamera", - "de": "Um was für eine Überwachungskamera handelt es sich" + "it": "Che cosa sorveglia questa videocamera" }, "mappings": [ { @@ -194,8 +192,7 @@ "en": "An outdoor, yet private area is surveilled (e.g. a parking lot, a fuel station, courtyard, entrance, private driveway, ...)", "nl": "Een buitenruimte met privaat karakter (zoals een privé-oprit, een parking, tankstation, ...)", "fr": "Une zone extérieure, mais privée, est surveillée (par exemple, un parking, une station-service, une cour, une entrée, une allée privée, etc.)", - "it": "Sorveglia un'area esterna di proprietà privata (un parcheggio, una stazione di servizio, un cortile, un ingresso, un vialetto privato, ...)", - "de": "Ein privater Außenbereich wird überwacht (z. B. ein Parkplatz, eine Tankstelle, ein Innenhof, ein Eingang, eine private Einfahrt, ...)" + "it": "Sorveglia un'area esterna di proprietà privata (un parcheggio, una stazione di servizio, un cortile, un ingresso, un vialetto privato, ...)" } }, { @@ -208,8 +205,7 @@ "nl": "Een private binnenruimte wordt bewaakt, bv. een winkel, een parkeergarage, ...", "en": "A private indoor area is surveilled, e.g. a shop, a private underground parking, ...", "fr": "Une zone intérieure privée est surveillée, par exemple un magasin, un parking souterrain privé…", - "it": "Sorveglia un ambiente interno di proprietà privata, per esempio un negozio, un parcheggio sotterraneo privato, ...", - "de": "Ein privater Innenbereich wird überwacht, z. B. ein Geschäft, eine private Tiefgarage, ..." + "it": "Sorveglia un ambiente interno di proprietà privata, per esempio un negozio, un parcheggio sotterraneo privato, ..." } } ], @@ -220,8 +216,7 @@ "en": "Is the public space surveilled by this camera an indoor or outdoor space?", "nl": "Bevindt de bewaakte publieke ruimte camera zich binnen of buiten?", "fr": "L'espace public surveillé par cette caméra est-il un espace intérieur ou extérieur ?", - "it": "Lo spazio pubblico sorvegliato da questa videocamera è all'aperto o al chiuso?", - "de": "Handelt es sich bei dem von dieser Kamera überwachten öffentlichen Raum um einen Innen- oder Außenbereich?" + "it": "Lo spazio pubblico sorvegliato da questa videocamera è all'aperto o al chiuso?" }, "condition": { "and": [ @@ -424,8 +419,7 @@ "en": "This camera is placed against a wall", "nl": "Deze camera hangt aan een muur", "fr": "Cette caméra est placée contre un mur", - "it": "Questa telecamera è posizionata contro un muro", - "de": "Diese Kamera ist an einer Wand montiert" + "it": "Questa telecamera è posizionata contro un muro" } }, { @@ -434,8 +428,7 @@ "en": "This camera is placed one a pole", "nl": "Deze camera staat op een paal", "fr": "Cette caméra est placée sur un poteau", - "it": "Questa telecamera è posizionata su un palo", - "de": "Diese Kamera ist an einer Stange montiert" + "it": "Questa telecamera è posizionata su un palo" } }, { @@ -444,8 +437,7 @@ "en": "This camera is placed on the ceiling", "nl": "Deze camera hangt aan het plafond", "fr": "Cette caméra est placée au plafond", - "it": "Questa telecamera è posizionata sul soffitto", - "de": "Diese Kamera ist an der Decke montiert" + "it": "Questa telecamera è posizionata sul soffitto" } } ], @@ -511,5 +503,65 @@ "title": "Surveillance camera" } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/surveillance/logo.svg", + "mappings": [ + { + "if": "camera:type=dome", + "then": "./assets/themes/surveillance/dome.svg" + }, + { + "if": "_direction:leftright=right", + "then": "./assets/themes/surveillance/cam_right.svg" + }, + { + "if": "_direction:leftright=left", + "then": "./assets/themes/surveillance/cam_left.svg" + } + ] + }, + "iconSize": { + "mappings": [ + { + "if": "camera:type=dome", + "then": "50,50,center" + }, + { + "if": "_direction:leftright~*", + "then": "100,35,center" + } + ], + "render": "50,50,center" + }, + "location": [ + "point", + "centroid" + ], + "rotation": { + "#": "Note: {camera:direction} is substituted by a number, giving the string 'calc(123deg + 90deg)' ; it is this string that is used as css property, which interprets the calc", + "render": "calc({_direction:numerical}deg + 90deg)", + "mappings": [ + { + "if": "camera:type=dome", + "then": "0" + }, + { + "if": "_direction:leftright=right", + "then": "calc({_direction:numerical}deg - 90deg)" + } + ] + } + }, + { + "color": { + "render": "#f00" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index e3da8e418..0ac8e08e4 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -412,24 +412,21 @@ "id": "toilet-handwashing", "question": { "en": "Do these toilets have a sink to wash your hands?", - "nl": "Hebben deze toiletten een lavabo om de handen te wassen?", - "de": "Verfügt diese Toilette über ein Waschbecken?" + "nl": "Hebben deze toiletten een lavabo om de handen te wassen?" }, "mappings": [ { "if": "toilets:handwashing=yes", "then": { "en": "This toilets have a sink to wash your hands", - "nl": "Deze toiletten hebben een lavabo waar men de handen kan wassen", - "de": "Diese Toilette verfügt über ein Waschbecken" + "nl": "Deze toiletten hebben een lavabo waar men de handen kan wassen" } }, { "if": "toilets:handwashing=no", "then": { "en": "This toilets don't have a sink to wash your hands", - "nl": "Deze toiletten hebben geen lavabo waar men de handen kan wassen", - "de": "Diese Toilette verfügt über kein Waschbecken" + "nl": "Deze toiletten hebben geen lavabo waar men de handen kan wassen" } } ] @@ -438,8 +435,7 @@ "id": "toilet-has-paper", "question": { "en": "Does one have to bring their own toilet paper to this toilet?", - "nl": "Moet je je eigen toiletpappier meenemen naar deze toilet?", - "de": "Muss man für diese Toilette sein eigenes Toilettenpapier mitbringen?" + "nl": "Moet je je eigen toiletpappier meenemen naar deze toilet?" }, "mappings": [ { @@ -453,8 +449,7 @@ "if": "toilets:paper_supplied=no", "then": { "en": "You have to bring your own toilet paper to this toilet", - "nl": "Je moet je eigen toiletpapier meebrengen naar deze toilet", - "de": "Für diese Toilette müssen Sie Ihr eigenes Toilettenpapier mitbringen" + "nl": "Je moet je eigen toiletpapier meebrengen naar deze toilet" } } ] @@ -519,5 +514,30 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/toilet/toilets.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:white;./assets/layers/toilet/wheelchair.svg" + }, + { + "if": { + "or": [ + "toilets:position=urinals", + "toilets:position=urinal" + ] + }, + "then": "./assets/layers/toilet/urinal.svg" + } + ] + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/trail/trail.json b/assets/layers/trail/trail.json index 79c0c9217..9f36afb2d 100644 --- a/assets/layers/trail/trail.json +++ b/assets/layers/trail/trail.json @@ -40,8 +40,7 @@ "id": "trail-length", "render": { "en": "The trail is {_length:km} kilometers long", - "nl": "Deze wandeling is {_length:km} kilometer lang", - "de": "Der Wanderweg ist {_length:km} Kilometer lang" + "nl": "Deze wandeling is {_length:km} kilometer lang" } }, { @@ -107,32 +106,28 @@ "if": "colour=blue", "then": { "nl": "Blauwe wandeling", - "en": "Blue trail", - "de": "Blauer Weg" + "en": "Blue trail" } }, { "if": "colour=red", "then": { "nl": "Rode wandeling", - "en": "Red trail", - "de": "Roter Weg" + "en": "Red trail" } }, { "if": "colour=green", "then": { "nl": "Groene wandeling", - "en": "Green trail", - "de": "Grüner Weg" + "en": "Green trail" } }, { "if": "colour=yellow", "then": { "nl": "Gele wandeling", - "en": "Yellow trail", - "de": "Gelber Weg" + "en": "Yellow trail" } } ], @@ -213,5 +208,45 @@ }, "dashArray": { "render": "5 5" - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/trail/trail.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "./assets/layers/trail/wheelchair.svg" + }, + { + "if": "pushchair=yes", + "then": "./assets/layers/trail/pushchair.svg" + } + ] + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#335D9F", + "mappings": [ + { + "if": "colour~*", + "then": "{colour}" + } + ] + }, + "width": { + "render": "3" + }, + "dashArray": { + "render": "5 5" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index dd75dd525..968ecb3ca 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -82,8 +82,7 @@ "nl": "Is dit een naald- of loofboom?", "en": "Is this a broadleaved or needleleaved tree?", "it": "Si tratta di un albero latifoglia o aghifoglia?", - "fr": "Cet arbre est-il un feuillu ou un résineux ?", - "de": "Ist dies ein Laub- oder Nadelbaum?" + "fr": "Cet arbre est-il un feuillu ou un résineux ?" }, "mappings": [ { @@ -137,8 +136,7 @@ "nl": "Hoe significant is deze boom? Kies het eerste antwoord dat van toepassing is.", "en": "How significant is this tree? Choose the first answer that applies.", "it": "Quanto significativo è questo albero? Scegli la prima risposta che corrisponde.", - "fr": "Quelle est l'importance de cet arbre ? Choisissez la première réponse qui s'applique.", - "de": "Wie bedeutsam ist dieser Baum? Wählen Sie die erste Antwort, die zutrifft." + "fr": "Quelle est l'importance de cet arbre ? Choisissez la première réponse qui s'applique." }, "mappings": [ { @@ -151,8 +149,7 @@ "nl": "De boom valt op door zijn grootte of prominente locatie. Hij is nuttig voor navigatie.", "en": "The tree is remarkable due to its size or prominent location. It is useful for navigation.", "it": "È un albero notevole per le sue dimensioni o per la posizione prominente. È utile alla navigazione.", - "fr": "L'arbre est remarquable en raison de sa taille ou de son emplacement proéminent. Il est utile pour la navigation.", - "de": "Der Baum ist aufgrund seiner Größe oder seiner markanten Lage bedeutsam. Er ist nützlich zur Orientierung." + "fr": "L'arbre est remarquable en raison de sa taille ou de son emplacement proéminent. Il est utile pour la navigation." } }, { @@ -165,8 +162,7 @@ "nl": "De boom is een natuurlijk monument, bijvoorbeeld doordat hij bijzonder oud of van een waardevolle soort is.", "en": "The tree is a natural monument, e.g. because it is especially old, or of a valuable species.", "it": "L’albero è un monumento naturale, ad esempio perché specialmente antico o appartenente a specie importanti.", - "fr": "Cet arbre est un monument naturel (ex : âge, espèce, etc…)", - "de": "Der Baum ist ein Naturdenkmal, z. B. weil er besonders alt ist oder zu einer wertvollen Art gehört." + "fr": "Cet arbre est un monument naturel (ex : âge, espèce, etc…)" } }, { @@ -179,8 +175,7 @@ "nl": "De boom wordt voor landbouwdoeleinden gebruikt, bijvoorbeeld in een boomgaard.", "en": "The tree is used for agricultural purposes, e.g. in an orchard.", "it": "L’albero è usato per scopi agricoli, ad esempio in un frutteto.", - "fr": "Cet arbre est utilisé à but d’agriculture (ex : dans un verger)", - "de": "Der Baum wird für landwirtschaftliche Zwecke genutzt, z. B. in einer Obstplantage." + "fr": "Cet arbre est utilisé à but d’agriculture (ex : dans un verger)" } }, { @@ -193,8 +188,7 @@ "nl": "De boom staat in een park of dergelijke (begraafplaats, schoolterrein, …).", "en": "The tree is in a park or similar (cemetery, school grounds, …).", "it": "L’albero è in un parco o qualcosa di simile (cimitero, aree didattiche, etc.).", - "fr": "Cet arbre est dans un parc ou une aire similaire (ex : cimetière, cour d’école, …).", - "de": "Der Baum steht in einem Park oder ähnlichem (Friedhof, Schulgelände, ...)." + "fr": "Cet arbre est dans un parc ou une aire similaire (ex : cimetière, cour d’école, …)." } }, { @@ -220,8 +214,7 @@ "nl": "Dit is een laanboom.", "en": "This is a tree along an avenue.", "it": "Fa parte di un viale alberato.", - "fr": "C'est un arbre le long d'une avenue.", - "de": "Dieser Baum steht entlang einer Straße." + "fr": "C'est un arbre le long d'une avenue." } }, { @@ -247,8 +240,7 @@ "nl": "De boom staat buiten een woonkern.", "en": "The tree is outside of an urban area.", "it": "L’albero si trova fuori dall’area urbana.", - "fr": "Cet arbre est en zone rurale.", - "de": "Dieser Baum steht außerhalb eines städtischen Gebiets." + "fr": "Cet arbre est en zone rurale." } } ] @@ -275,8 +267,7 @@ "en": "Deciduous: the tree loses its leaves for some time of the year.", "it": "Caduco: l’albero perde le sue foglie per un periodo dell’anno.", "ru": "Листопадное: у дерева опадают листья в определённое время года.", - "fr": "Caduc : l’arbre perd son feuillage une partie de l’année.", - "de": "Laubabwerfend: Der Baum verliert für eine gewisse Zeit des Jahres seine Blätter." + "fr": "Caduc : l’arbre perd son feuillage une partie de l’année." } }, { @@ -373,8 +364,7 @@ "nl": "\"\"/ Erkend als houtig erfgoed door Onroerend Erfgoed Vlaanderen", "en": "\"\"/ Registered as heritage by Onroerend Erfgoed Flanders", "it": "\"\"/Registrato come patrimonio da Onroerend Erfgoed Flanders", - "fr": "\"\"/ Fait partie du patrimoine par Onroerend Erfgoed", - "de": "\"\"/ Als Denkmal registriert von der Onroerend Erfgoed Flandern" + "fr": "\"\"/ Fait partie du patrimoine par Onroerend Erfgoed" } }, { @@ -388,8 +378,7 @@ "nl": "Erkend als natuurlijk erfgoed door Directie Cultureel Erfgoed Brussel", "en": "Registered as heritage by Direction du Patrimoine culturel Brussels", "it": "Registrato come patrimonio da Direction du Patrimoine culturel di Bruxelles", - "fr": "Enregistré comme patrimoine par la Direction du Patrimoine culturel Bruxelles", - "de": "Als Denkmal registriert von der Direction du Patrimoine culturel Brüssel" + "fr": "Enregistré comme patrimoine par la Direction du Patrimoine culturel Bruxelles" } }, { @@ -455,8 +444,7 @@ "nl": "Wat is het ID uitgegeven door Onroerend Erfgoed Vlaanderen?", "en": "What is the ID issued by Onroerend Erfgoed Flanders?", "it": "Qual è l’ID rilasciato da Onroerend Erfgoed Flanders?", - "fr": "Quel est son identifiant donné par Onroerend Erfgoed ?", - "de": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?" + "fr": "Quel est son identifiant donné par Onroerend Erfgoed ?" }, "freeform": { "key": "ref:OnroerendErfgoed", @@ -482,8 +470,7 @@ "nl": "Wat is het Wikidata-ID van deze boom?", "en": "What is the Wikidata ID for this tree?", "it": "Qual è l’ID Wikidata per questo albero?", - "fr": "Quel est l'identifiant Wikidata de cet arbre ?", - "de": "Was ist das passende Wikidata Element zu diesem Baum?" + "fr": "Quel est l'identifiant Wikidata de cet arbre ?" }, "freeform": { "key": "wikidata", @@ -612,5 +599,36 @@ }, "deletion": { "minNeededChangesets": 5 - } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:#ffffff;./assets/themes/trees/unknown.svg", + "mappings": [ + { + "if": { + "and": [ + "leaf_type=broadleaved" + ] + }, + "then": "circle:#ffffff;./assets/themes/trees/broadleaved.svg" + }, + { + "if": { + "and": [ + "leaf_type=needleleaved" + ] + }, + "then": "circle:#ffffff;./assets/themes/trees/needleleaved.svg" + } + ] + }, + "iconSize": { + "render": "40,40,bottom" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/type_node/type_node.json b/assets/layers/type_node/type_node.json new file mode 100644 index 000000000..4a61d8232 --- /dev/null +++ b/assets/layers/type_node/type_node.json @@ -0,0 +1,12 @@ +{ + "id": "type_node", + "description": "This is a special meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list", + "minzoom": 18, + "source": { + "osmTags": "id~node/.*" + }, + "mapRendering": [], + "name": "All OSM Nodes", + "title": "OSM node {id}", + "tagRendering": [] +} \ No newline at end of file diff --git a/assets/layers/viewpoint/viewpoint.json b/assets/layers/viewpoint/viewpoint.json index 6f34c3159..0a5342709 100644 --- a/assets/layers/viewpoint/viewpoint.json +++ b/assets/layers/viewpoint/viewpoint.json @@ -70,5 +70,18 @@ }, "id": "viewpoint-description" } + ], + "mapRendering": [ + { + "icon": "./assets/layers/viewpoint/viewpoint.svg", + "iconSize": "20,20,center", + "location": [ + "point" + ] + }, + { + "color": "#ffffff", + "width": "5" + } ] } \ No newline at end of file diff --git a/assets/layers/village_green/village_green.json b/assets/layers/village_green/village_green.json index 9aa60fca4..24b12263c 100644 --- a/assets/layers/village_green/village_green.json +++ b/assets/layers/village_green/village_green.json @@ -35,5 +35,19 @@ "id": "village_green-reviews", "render": "{reviews(name, landuse=village_green )}" } + ], + "mapRendering": [ + { + "icon": "./assets/themes/playgrounds/playground.svg", + "iconSize": "40,40,center", + "location": [ + "point", + "centroid" + ] + }, + { + "color": "#937f20", + "width": "1" + } ] } \ No newline at end of file diff --git a/assets/layers/visitor_information_centre/visitor_information_centre.json b/assets/layers/visitor_information_centre/visitor_information_centre.json index 28986dd8a..ceddeef3c 100644 --- a/assets/layers/visitor_information_centre/visitor_information_centre.json +++ b/assets/layers/visitor_information_centre/visitor_information_centre.json @@ -67,5 +67,18 @@ "render": "#E64C00" }, "presets": [], - "wayHandling": 1 + "wayHandling": 1, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/visitor_information_centre/information.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/waste_basket/waste_basket.json b/assets/layers/waste_basket/waste_basket.json index 7a3691a97..3e89c9e5a 100644 --- a/assets/layers/waste_basket/waste_basket.json +++ b/assets/layers/waste_basket/waste_basket.json @@ -32,8 +32,7 @@ "id": "waste-basket-waste-types", "question": { "en": "What kind of waste basket is this?", - "nl": "Wat voor soort vuilnisbak is dit?", - "de": "Um was für einen Abfalleimer handelt es sich?" + "nl": "Wat voor soort vuilnisbak is dit?" }, "multiAnswer": true, "mappings": [ @@ -91,8 +90,7 @@ "id": "dispensing_dog_bags", "question": { "en": "Does this waste basket have a dispenser for dog excrement bags?", - "nl": "Heeft deze vuilnisbak een verdeler voor hondenpoepzakjes?", - "de": "Verfügt dieser Abfalleimer über einen Spender für (Hunde-)Kotbeutel?" + "nl": "Heeft deze vuilnisbak een verdeler voor hondenpoepzakjes?" }, "condition": { "or": [ @@ -111,8 +109,7 @@ }, "then": { "en": "This waste basket has a dispenser for (dog) excrement bags", - "nl": "Deze vuilnisbak heeft een verdeler voor hondenpoepzakjes", - "de": "Dieser Abfalleimer verfügt über einen Spender für (Hunde-)Kotbeutel" + "nl": "Deze vuilnisbak heeft een verdeler voor hondenpoepzakjes" } }, { @@ -124,16 +121,14 @@ }, "then": { "en": "This waste basket does not have a dispenser for (dog) excrement bags", - "nl": "Deze vuilbak heeft geen verdeler voor hondenpoepzakjes", - "de": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" + "nl": "Deze vuilnisbak heeft geenverdeler voor hondenpoepzakjes" } }, { "if": "vending=", "then": { "en": "This waste basket does not have a dispenser for (dog) excrement bags", - "nl": "Deze vuilnisbak heeft geen verdeler voor hondenpoepzakjes", - "de": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" + "nl": "Deze vuilnisbaak heeft waarschijnlijk geen verdeler voor hondenpoepzakjes" }, "hideInAnwer": true } @@ -195,5 +190,41 @@ "allowMove": { "enableRelocation": false, "enableImproveAccuraccy": true - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/waste_basket/waste_basket.svg" + }, + "iconSize": { + "render": "40,40,center", + "mappings": [ + { + "if": { + "and": [ + "amenity=waste_basket" + ] + }, + "then": { + "en": "Waste Basket", + "nl": "Vuilnisbak", + "ru": "Контейнер для мусора", + "de": "Abfalleimer" + } + } + ] + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } \ No newline at end of file diff --git a/assets/layers/watermill/watermill.json b/assets/layers/watermill/watermill.json index 1804424a7..1764ccc4f 100644 --- a/assets/layers/watermill/watermill.json +++ b/assets/layers/watermill/watermill.json @@ -171,5 +171,18 @@ }, "color": { "render": "#FFC0CB" - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/watermill/watermill.svg" + }, + "iconSize": { + "render": "50,50,center" + }, + "location": [ + "point" + ] + } + ] } \ No newline at end of file diff --git a/assets/svg/liberapay.svg b/assets/svg/liberapay.svg new file mode 100644 index 000000000..23a0df206 --- /dev/null +++ b/assets/svg/liberapay.svg @@ -0,0 +1 @@ + diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 4ac61d303..a3e38008d 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -879,6 +879,16 @@ ], "sources": [] }, + { + "path": "liberapay.svg", + "license": "Logo (all rights reserved)", + "authors": [ + "LiberaPay" + ], + "sources": [ + "https://liberapay.com/" + ] + }, { "path": "loading.svg", "license": "CC0; trivial", @@ -1411,6 +1421,26 @@ "https://www.onlinewebfonts.com/icon/197818" ] }, + { + "path": "teardrop.svg", + "license": "CC-BY-SA 3.0 Unported", + "authors": [ + "Mono, derivated work User:Benoit Rochon" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Map_pin_icon_green.svg" + ] + }, + { + "path": "teardrop_with_hole_green.svg", + "license": "CC-BY-SA 3.0 Unported", + "authors": [ + "Mono, derivated work User:Benoit Rochon" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Map_pin_icon_green.svg" + ] + }, { "path": "translate.svg", "license": "CC-BY-SA 3.0", diff --git a/assets/svg/teardrop.svg b/assets/svg/teardrop.svg new file mode 100644 index 000000000..1cc113c57 --- /dev/null +++ b/assets/svg/teardrop.svg @@ -0,0 +1,104 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/assets/svg/teardrop_with_hole_green.svg b/assets/svg/teardrop_with_hole_green.svg new file mode 100644 index 000000000..cc32242cd --- /dev/null +++ b/assets/svg/teardrop_with_hole_green.svg @@ -0,0 +1,130 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/assets/themes/aed/aed_brugge.json b/assets/themes/aed/aed_brugge.json index 2ccf774d8..0de0fcb89 100644 --- a/assets/themes/aed/aed_brugge.json +++ b/assets/themes/aed/aed_brugge.json @@ -15,9 +15,6 @@ "startLat": 51.25634, "startLon": 3.195682, "startZoom": 12, - "clustering": { - "maxZoom": 0 - }, "layers": [ "defibrillator", { @@ -45,9 +42,30 @@ "iconSize": "20,20,center", "tagRenderings": [ "all_tags" + ], + "mapRendering": [ + { + "icon": { + "render": "circle:red", + "mappings": [ + { + "if": "_has_closeby_feature=yes", + "then": "circle:#008000aa" + } + ] + }, + "iconSize": "20,20,center", + "location": [ + "point" + ] + }, + {} ] } ], "hideFromOverview": true, - "defaultBackgroundId": "HDM_HOT" + "defaultBackgroundId": "HDM_HOT", + "clustering": { + "maxZoom": 0 + } } \ No newline at end of file diff --git a/assets/themes/buurtnatuur/buurtnatuur.json b/assets/themes/buurtnatuur/buurtnatuur.json index 0f1c8648a..dde19b991 100644 --- a/assets/themes/buurtnatuur/buurtnatuur.json +++ b/assets/themes/buurtnatuur/buurtnatuur.json @@ -123,6 +123,51 @@ "nl": "Voeg een ontbrekend, erkend natuurreservaat toe, bv. een gebied dat beheerd wordt door het ANB of natuurpunt" } } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:#ffffff;./assets/themes/buurtnatuur/nature_reserve.svg" + }, + "iconSize": { + "render": "50,50,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#3c3", + "mappings": [ + { + "if": { + "and": [ + "name=", + "noname=", + "operator=", + "access=", + "access:description=", + "leisure=park" + ] + }, + "then": "#cc1100" + }, + { + "if": { + "and": [ + "name=", + "noname=" + ] + }, + "then": "#fccb37" + } + ] + }, + "width": { + "render": "5" + } + } ] }, { @@ -221,6 +266,38 @@ "nl": "Voeg een ontbrekend park toe" } } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:#ffffff;./assets/themes/buurtnatuur/park.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#3c3", + "mappings": [ + { + "if": { + "and": [ + "name=", + "noname=" + ] + }, + "then": "#fccb37" + } + ] + }, + "width": { + "render": "5" + } + } ] }, { @@ -338,6 +415,56 @@ "nl": "Voeg een ontbrekend bos toe aan de kaart" } } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:#ffffff;./assets/themes/buurtnatuur/forest.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#3a3", + "mappings": [ + { + "if": { + "and": [ + "operator=", + "access=", + "access:description=" + ] + }, + "then": "#cc1100" + }, + { + "if": { + "and": [ + "operator=" + ] + }, + "then": "#cccc00" + }, + { + "if": { + "and": [ + "name=", + "noname=" + ] + }, + "then": "#fccb37" + } + ] + }, + "width": { + "render": "5" + } + } ] }, "viewpoint" diff --git a/assets/themes/campersite/campersite.json b/assets/themes/campersite/campersite.json index 1b060b824..7a572044f 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -304,8 +304,7 @@ "zh_Hant": "{capacity} 露營者能夠同時使用這個地方", "fr": "{capacity} personnes peuvent utiliser cet espace en même temps", "pt_BR": "{capacity} campistas podem usar este lugar ao mesmo tempo", - "de": "{capacity} Wohnmobile können diesen Platz gleichzeitig nutzen", - "nl": "{capacity} campers kunnen deze plaats tegelijk gebruiken" + "de": "{capacity} Wohnmobile können diesen Platz gleichzeitig nutzen" }, "question": { "en": "How many campers can stay here? (skip if there is no obvious number of spaces or allowed vehicles)", @@ -315,8 +314,7 @@ "zh_Hant": "多少露營者能夠待在這裡?(如果沒有明顯的空間數字或是允許車輛則可以跳過)", "fr": "Combien de personnes peuvent camper ici ? (Passez s’il n’y a pas de places délimitées)", "pt_BR": "Quantos campistas podem ficar aqui? (pule se não houver um número óbvio de vagas ou veículos permitidos)", - "de": "Wie viele Wohnmobile können hier parken? (Überspringen, wenn es keine offensichtliche Anzahl von Stellplätzen oder erlaubten Fahrzeugen gibt)", - "nl": "Hoeveel campers kunnen hier overnachten? (sla dit over als er geen duidelijk aantal plaatsen of aangeduid maximum is)" + "de": "Wie viele Wohnmobile können hier parken? (Überspringen, wenn es keine offensichtliche Anzahl von Stellplätzen oder erlaubten Fahrzeugen gibt)" }, "freeform": { "key": "capacity", @@ -353,8 +351,7 @@ "zh_Hant": "這裡有網路連線", "fr": "Il y a un accès internet", "pt_BR": "Há acesso à internet", - "de": "Internetzugang ist vorhanden", - "nl": "Er is internettoegang" + "de": "Internetzugang ist vorhanden" } }, { @@ -373,8 +370,7 @@ "zh_Hant": "這裡有網路連線", "fr": "Il y a un accès internet", "pt_BR": "Há acesso à Internet", - "de": "Internetzugang ist vorhanden", - "nl": "Er is internettoegang" + "de": "Internetzugang ist vorhanden" }, "hideInAnswer": true }, @@ -613,8 +609,7 @@ "it": "Maggiori dettagli su questo luogo: {description}", "fr": "Plus de détails à propos du site : {description}", "pt_BR": "Mais detalhes sobre este lugar: {description}", - "de": "Mehr Details über diesen Ort: {description}", - "nl": "Meer details over deze plaats: {description}" + "de": "Mehr Details über diesen Ort: {description}" }, "question": { "en": "Would you like to add a general description of this place? (Do not repeat information previously asked or shown above. Please keep it objective - opinions go into the reviews)", @@ -623,8 +618,7 @@ "zh_Hant": "你想要為這個地方加一般的敘述嗎?(不要重覆加先前問過或提供的資訊,請保持敘述性-請將意見留在評價)", "it": "Desideri aggiungere una descrizione del luogo? (Non vanno ripetute informazioni già richieste e mostrate precedentemente. Si prega di attenersi a dati oggettivi - le opinioni vanno nelle recensioni)", "fr": "Souhaitez-vous ajouter une description générale du lieu ? (Ne pas répéter les informations précédentes et rester neutre, les opinions vont dans les avis)", - "de": "Möchten Sie eine allgemeine Beschreibung für diesen Ort hinzufügen? (Bitte wiederholen Sie keine Informationen, die Sie bereits zuvor angegeben haben. Bitte bleiben Sie objektiv - Meinungen gehen in die Bewertungen ein)", - "nl": "Wil je graag een algemene beschrijving toevoegen van deze plaats? (Herhaal hier niet de antwoorden op de vragen die reeds gesteld zijn. Hou het objectief - je kan je mening geven via een review)" + "de": "Möchten Sie eine allgemeine Beschreibung für diesen Ort hinzufügen? (Bitte wiederholen Sie keine Informationen, die Sie bereits zuvor angegeben haben. Bitte bleiben Sie objektiv - Meinungen gehen in die Bewertungen ein)" }, "freeform": { "key": "description", @@ -670,8 +664,7 @@ "it": "luogo di campeggio", "fr": "Aire de camping", "pt_BR": "local de acampamento", - "de": "Wohnmobilstellplatz", - "nl": "camperplaats" + "de": "Wohnmobilstellplatz" }, "description": { "en": "Add a new official camper site. These are designated places to stay overnight with your camper. They might look like a real camping or just look like a parking. They might not be signposted at all, but just be defined in a municipal decision. A regular parking intended for campers where it is not expected to spend the night, is -not- a camper site ", @@ -679,12 +672,43 @@ "ja": "新しい公式キャンプサイトを追加します。お客様のキャンピングカーで一泊する指定の場所です。本物のキャンプのように見えるかもしれないし、単なる駐車場のように見えるかもしれない。それらは全く署名されていないかもしれませんが、自治体の決定で定義されているだけです。夜を過ごすことが予想されないキャンパー向けの通常の駐車場は、キャンプサイトではない ", "it": "Aggiungi una nuova area di sosta ufficiale per camper. Si tratta di aree destinate alla sosta notturna dei camper. Potrebbe trattarsi di luoghi di campeggio o semplici parcheggi. Potrebbero anche non essere segnalati sul posto, ma semplicemente indicati in una delibera comunale. Un parcheggio destinato ai camper in cui non è però consentito trascorrere la notte -non- va considerato un'area di sosta per camper. ", "fr": "Ajouter une nouvelle aire de camping officielle, destinée à y passer la nuit avec un camping-car. Elle ne nécessite pas d’infrastructures particulières et peut être simplement désignée sous arrêté municipal, un simple parking ne suffit pas à rentrer dans cette catégorie ", - "de": "Fügen Sie einen neuen offiziellen Wohnmobilstellplatz hinzu. Dies sind ausgewiesene Plätze, an denen Sie in Ihrem Wohnmobil übernachten können. Sie können wie ein richtiger Campingplatz oder nur wie ein Parkplatz aussehen. Möglicherweise sind sie gar nicht ausgeschildert, sondern nur in einem Gemeindebeschluss festgelegt. Ein normaler Parkplatz für Wohnmobile, auf dem Übernachten nicht zulässig ist, ist kein Wohnmobilstellplatz. ", - "nl": "Voeg een nieuwe officiële camperplaats toe. Dit zijn speciaal aangeduide plaatsen waar het toegestaan is om te overnachten met een camper. Ze kunnen er uitzien als een parking, of soms eerder als een camping. Soms staan ze niet ter plaatse aangeduid, maar heeft de gemeente wel degelijk beslist dat dit een camperplaats is. Een parking voor campers waar je niet mag overnachten is géén camperplaats. " + "de": "Fügen Sie einen neuen offiziellen Wohnmobilstellplatz hinzu. Dies sind ausgewiesene Plätze, an denen Sie in Ihrem Wohnmobil übernachten können. Sie können wie ein richtiger Campingplatz oder nur wie ein Parkplatz aussehen. Möglicherweise sind sie gar nicht ausgeschildert, sondern nur in einem Gemeindebeschluss festgelegt. Ein normaler Parkplatz für Wohnmobile, auf dem übernachten nicht zulässig ist, ist kein Wohnmobilstellplatz. " } } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/themes/campersite/caravan.svg", + "mappings": [ + { + "if": { + "and": [ + "fee=no" + ] + }, + "then": "circle:white;./assets/themes/campersite/caravan_green.svg" + } + ] + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] }, { "id": "dumpstations", @@ -1083,6 +1107,27 @@ "de": "Fügen Sie eine neue sanitäre Entsorgungsstation hinzu. Hier können Camper Abwasser oder chemischen Toilettenabfälle entsorgen. Oft gibt es auch Trinkwasser und Strom." } } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/themes/campersite/sanitary_dump_station.svg" + }, + "iconSize": { + "render": "32,32,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } ] } ], diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index 76ae6841d..cd73e5e4b 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -156,8 +156,7 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "width": { @@ -215,7 +214,26 @@ } } ], - "wayHandling": 1 + "wayHandling": 1, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/climbing/club.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + } + ] }, { "id": "climbing_gym", @@ -305,15 +323,33 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "width": "0", "iconSize": { "render": "40,40,center" }, - "wayHandling": 1 + "wayHandling": 1, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/climbing/climbing_gym.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + } + ] }, { "id": "climbing_route", @@ -454,13 +490,11 @@ { "question": { "en": "How much bolts does this route have before reaching the moulinette?", - "fr": "Combien de prises cette voie possède avant d’atteindre la moulinette ?", - "de": "Wie viele Haken gibt es auf dieser Kletterroute bevor der Umlenker bzw. Standhaken erreicht ist?" + "fr": "Combien de prises cette voie possède avant d’atteindre la moulinette ?" }, "render": { "en": "This route has {climbing:bolts} bolts", - "fr": "Cette voie a {climbing:bolts} prises", - "de": "Diese Kletterroute hat {climbing:bolts} Haken" + "fr": "Cette voie a {climbing:bolts} prises" }, "freeform": { "key": "climbing:bolts", @@ -474,8 +508,7 @@ "if": "climbing:bolted=no", "then": { "en": "This route is not bolted", - "fr": "Cette voie n’a pas de prises", - "de": "Auf dieser Kletterroute sind keine Haken vorhanden" + "fr": "Cette voie n’a pas de prises" }, "hideInAnswer": true }, @@ -483,8 +516,7 @@ "if": "climbing:bolted=no&climbing:bolts=", "then": { "en": "This route is not bolted", - "fr": "Cette voie n’a pas de prises", - "de": "Auf dieser Kletterroute sind keine Haken vorhanden" + "fr": "Cette voie n’a pas de prises" } } ], @@ -536,7 +568,29 @@ ] } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/themes/climbing/climbing_route.svg" + }, + "iconSize": { + "render": "28,28,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#0f0" + }, + "width": { + "render": "4" + } + } + ] }, { "id": "climbing", @@ -805,6 +859,28 @@ "_difficulty_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:grade:french'])", "_length_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:length'])", "_contained_climbing_routes_count=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').length" + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/climbing/climbing_no_rope.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#d38d5fAA" + }, + "width": { + "render": "8" + } + } ] }, { @@ -934,7 +1010,23 @@ "color": { "render": "#ddff55AA" }, - "wayHandling": 0 + "wayHandling": 0, + "mapRendering": [ + { + "icon": "./assets/themes/climbing/climbing_unknown.svg", + "location": [ + "point" + ] + }, + { + "color": { + "render": "#ddff55AA" + }, + "width": { + "render": "2" + } + } + ] } ], "overrideAll": { diff --git a/assets/themes/cycle_highways/cycle_highways.json b/assets/themes/cycle_highways/cycle_highways.json index 35f49b110..0954bc838 100644 --- a/assets/themes/cycle_highways/cycle_highways.json +++ b/assets/themes/cycle_highways/cycle_highways.json @@ -233,6 +233,43 @@ } ] } + ], + "mapRendering": [ + { + "color": { + "render": "#ff7392", + "mappings": [ + { + "if": "state=", + "then": "#00acfc" + }, + { + "if": "state=temporary", + "then": "#00acfc" + } + ] + }, + "width": { + "render": "4" + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": "state=temporary", + "then": "12 10" + }, + { + "if": "note:state=has_highway_no", + "then": "0 8" + }, + { + "if": "note:state=has_highway_under_construction", + "then": "12 10" + } + ] + } + } ] } ], diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 9f332e157..406d7cf9a 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -84,6 +84,18 @@ "width": "10", "tagRenderings": [ "images" + ], + "mapRendering": [ + { + "icon": "./assets/themes/cyclestreets/F111.svg", + "location": [ + "point" + ] + }, + { + "color": "#0000ff", + "width": "10" + } ] }, { @@ -136,6 +148,18 @@ "width": "5", "tagRenderings": [ "images" + ], + "mapRendering": [ + { + "icon": "./assets/themes/cyclestreets/F113.svg", + "location": [ + "point" + ] + }, + { + "color": "#09f9dd", + "width": "5" + } ] }, { @@ -201,6 +225,30 @@ }, "tagRenderings": [ "images" + ], + "mapRendering": [ + { + "icon": "./assets/svg/pencil.svg", + "location": [ + "point" + ] + }, + { + "color": { + "render": "#aaaaaa", + "mappings": [ + { + "then": "#0000ff", + "if": "cyclestreet=yes" + }, + { + "then": "#09f9dd", + "if": "proposed:cyclestreet=yes" + } + ] + }, + "width": "5" + } ] } ], diff --git a/assets/themes/facadegardens/facadegardens.json b/assets/themes/facadegardens/facadegardens.json index d9fc9ed62..c5fb32ff6 100644 --- a/assets/themes/facadegardens/facadegardens.json +++ b/assets/themes/facadegardens/facadegardens.json @@ -84,33 +84,27 @@ "iconOverlays": [ { "if": "plant~.*vine.*", - "then": "circle:white;./assets/themes/facadegardens/klimplant.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/klimplant.svg" }, { "if": "plant~.*groundcover.*", - "then": "circle:white;./assets/themes/facadegardens/bodembedekker.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/bodembedekker.svg" }, { "if": "edible=true", - "then": "circle:white;./assets/themes/facadegardens/eetbaar.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/eetbaar.svg" }, { "if": "rain_barel=yes", - "then": "circle:white;./assets/themes/facadegardens/gevelton.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/gevelton.svg" }, { "if": "plant~.*shrub.*", - "then": "circle:white;./assets/themes/facadegardens/struik.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/struik.svg" }, { "if": "plant~.*flower.*", - "then": "circle:white;./assets/themes/facadegardens/bloei.svg", - "badge": true + "then": "circle:white;./assets/themes/facadegardens/bloei.svg" } ], "tagRenderings": [ @@ -454,7 +448,72 @@ } } ], - "wayHandling": 1 + "wayHandling": 1, + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/themes/facadegardens/geveltuin.svg", + "mappings": [ + { + "if": { + "and": [ + "direct_sunlight=yes" + ] + }, + "then": "circle:white;./assets/themes/facadegardens/zon.svg" + }, + { + "if": { + "and": [ + "direct_sunlight=partial" + ] + }, + "then": "circle:white;./assets/themes/facadegardens/halfzon.svg" + }, + { + "if": { + "and": [ + "direct_sunlight=no" + ] + }, + "then": "circle:white;./assets/themes/facadegardens/schaduw.svg" + } + ] + }, + "iconBadges": [ + { + "if": "plant~.*vine.*", + "then": "circle:white;./assets/themes/facadegardens/klimplant.svg" + }, + { + "if": "plant~.*groundcover.*", + "then": "circle:white;./assets/themes/facadegardens/bodembedekker.svg" + }, + { + "if": "edible=true", + "then": "circle:white;./assets/themes/facadegardens/eetbaar.svg" + }, + { + "if": "rain_barel=yes", + "then": "circle:white;./assets/themes/facadegardens/gevelton.svg" + }, + { + "if": "plant~.*shrub.*", + "then": "circle:white;./assets/themes/facadegardens/struik.svg" + }, + { + "if": "plant~.*flower.*", + "then": "circle:white;./assets/themes/facadegardens/bloei.svg" + } + ], + "iconSize": { + "render": "50,50,center" + }, + "location": [ + "point" + ] + } + ] } ] } \ No newline at end of file diff --git a/assets/themes/fruit_trees/fruit_trees.json b/assets/themes/fruit_trees/fruit_trees.json index 9f9e84fab..b6af011b1 100644 --- a/assets/themes/fruit_trees/fruit_trees.json +++ b/assets/themes/fruit_trees/fruit_trees.json @@ -68,6 +68,27 @@ "nl": "Voeg een boomgaard toe (als punt - omtrek nog te tekenen)" } } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/buurtnatuur/forest.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } ] }, { @@ -170,6 +191,27 @@ "nl": "Voeg hier een boom toe" } } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/fruit_trees/fruit_tree.svg" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } ] } ] diff --git a/assets/themes/ghostbikes/ghostbikes.json b/assets/themes/ghostbikes/ghostbikes.json index 97c580617..f163271ca 100644 --- a/assets/themes/ghostbikes/ghostbikes.json +++ b/assets/themes/ghostbikes/ghostbikes.json @@ -53,6 +53,9 @@ "startLat": 0, "startLon": 0, "widenFactor": 5, + "clustering": { + "maxZoom": 0 + }, "layers": [ "ghost_bike" ], diff --git a/assets/themes/grb.json b/assets/themes/grb.json deleted file mode 100644 index 9c6252617..000000000 --- a/assets/themes/grb.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "id": "grb", - "title": { - "nl": "GRB Fixup" - }, - "shortDescription": { - "nl": "Grb Fixup" - }, - "description": { - "nl": "GRB Fixup" - }, - "language": [ - "nl" - ], - "maintainer": "", - "icon": "./assets/svg/bug.svg", - "version": "0", - "startLat": 51.2132, - "startLon": 3.231, - "startZoom": 14, - "widenFactor": 2, - "socialImage": "", - "layers": [ - { - "id": "grb-fixmes", - "name": { - "nl": "Fixmes op gebouwen" - }, - "minzoom": 12, - "source": { - "maxCacheAge": 0, - "osmTags": { - "and": [ - "fixme~*", - "building~*" - ] - } - }, - "calculatedTags": [ - "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]" - ], - "title": { - "render": { - "nl": "{addr:street} {addr:housenumber}" - }, - "mappings": [ - { - "if": { - "and": [ - "fixme~*" - ] - }, - "then": { - "nl": "{fixme}" - } - } - ] - }, - "description": { - "nl": "Dit gebouw heeft een foutmelding" - }, - "tagRenderings": [ - { - "id": "grb-housenumber", - "render": { - "nl": "Het huisnummer is {addr:housenumber}" - }, - "question": { - "nl": "Wat is het huisnummer?" - }, - "freeform": { - "key": "addr:housenumber" - }, - "mappings": [ - { - "if": { - "and": [ - "not:addr:housenumber=yes", - "addr:housenumber=" - ] - }, - "then": { - "nl": "Geen huisnummer" - } - }, - { - "if": { - "and": [ - "addr:housenumber:={_grbNumber}", - "fixme=" - ] - }, - "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB", - "hideInAnswer": { - "or": [ - "_grbNumber=", - "_grbNumber=none", - "_grbNumber=no number" - ] - } - }, - { - "if": { - "and": [ - "addr:housenumber=", - "not:addr:housenumber=yes", - "fixme=" - ] - }, - "then": "Dit gebouw heeft geen nummer, net zoals in het GRB", - "hideInAnswer": "_grbNumber!=no number" - } - ] - }, - { - "id": "grb-unit", - "question": "Wat is de wooneenheid-aanduiding?", - "render": { - "nl": "De wooneenheid-aanduiding is {addr:unit} " - }, - "freeform": { - "key": "addr:unit" - }, - "mappings": [ - { - "if": "addr:unit=", - "then": "Geen wooneenheid-nummer" - } - ] - }, - { - "id": "grb-street", - "render": { - "nl": "De straat is {addr:street}" - }, - "freeform": { - "key": "addr:street" - }, - "question": { - "nl": "Wat is de straat?" - } - }, - { - "id": "grb-fixme", - "render": { - "nl": "De fixme is {fixme}" - }, - "question": { - "nl": "Wat zegt de fixme?" - }, - "freeform": { - "key": "fixme" - }, - "mappings": [ - { - "if": { - "and": [ - "fixme=" - ] - }, - "then": { - "nl": "Geen fixme" - } - } - ] - }, - { - "id": "grb-min-level", - "render": { - "nl": "Dit gebouw begint maar op de {building:min_level} verdieping" - }, - "question": { - "nl": "Hoeveel verdiepingen ontbreken?" - }, - "freeform": { - "key": "building:min_level", - "type": "pnat" - } - } - ], - "label": { - "mappings": [ - { - "if": "addr:housenumber~*", - "then": "
{addr:housenumber}
" - } - ] - }, - "width": { - "render": "2" - }, - "iconSize": { - "render": "40,40,center" - }, - "dashes": "2 2", - "color": { - "render": "#00f" - }, - "wayHandling": 2, - "presets": [] - } - ], - "hideFromOverview": true, - "defaultBackgroundId": "AGIVFlandersGRB" -} \ No newline at end of file diff --git a/assets/themes/grb_import/README.md b/assets/themes/grb_import/README.md new file mode 100644 index 000000000..2ed7bfbbf --- /dev/null +++ b/assets/themes/grb_import/README.md @@ -0,0 +1,20 @@ + GRB Import helper +=================== + + +Preparing the CRAB dataset +-------------------------- + +```` +# The original data is downloaded from https://download.vlaanderen.be/Producten/Detail?id=447&title=CRAB_Adressenlijst# (the GML-file here ) +wget https://downloadagiv.blob.core.windows.net/crab-adressenlijst/GML/CRAB_Adressenlijst_GML.zip + +# Extract the zip file +unzip CRAB_Adressenlijst_GML.zip + +# convert the pesky GML file into geojson +ogr2ogr -progress -t_srs WGS84 -f \"GeoJson\" CRAB.geojson CrabAdr.gml + +# When done, this big file is sliced into tiles with the slicer script +node --max_old_space_size=8000 $(which ts-node) ~/git/MapComplete/scripts/slice.ts CRAB.geojson 18 ~/git/pietervdvn.github.io/CRAB_2021_10_26 +```` \ No newline at end of file diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json new file mode 100644 index 000000000..5a3d5d545 --- /dev/null +++ b/assets/themes/grb_import/grb.json @@ -0,0 +1,723 @@ +{ + "id": "grb", + "title": { + "nl": "GRB Fixup" + }, + "shortDescription": { + "nl": "Grb Fixup" + }, + "description": { + "nl": "GRB Fixup", + "en": "This theme is an attempt to help automating the GRB import.
Note that this is very hacky and 'steals' the GRB data from an external site; in order to do this, you need to install and activate this firefox extension for it to work." + }, + "language": [ + "nl", + "en" + ], + "maintainer": "", + "icon": "./assets/svg/bug.svg", + "version": "0", + "startLat": 51.2132, + "startLon": 3.231, + "startZoom": 14, + "widenFactor": 2, + "socialImage": "", + "clustering": { + "maxZoom": 15 + }, + "overrideAll": { + "minzoom": 18 + }, + "trackAllNodes": true, + "layers": [ + { + "builtin": "type_node", + "isShown": { + "render": "no" + }, + "override": { + "calculatedTags": [ + "_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false", + "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false", + "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)", + "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false" + ] + } + }, + { + "id": "OSM-buildings", + "name": "All OSM-buildings", + "source": { + "osmTags": "building~*", + "maxCacheAge": 0 + }, + "mapRendering": [ + { + "width": { + "render": "2" + }, + "color": { + "render": "#00c", + "mappings": [ + { + "if": "building=house", + "then": "#a00" + }, + { + "if": "building=shed", + "then": "#563e02" + }, + { + "if": { + "or": [ + "building=garage", + "building=garages" + ] + }, + "then": "#f9bfbb" + }, + { + "if": "building=yes", + "then": "#0774f2" + } + ] + } + } + ], + "title": "OSM-gebouw", + "tagRenderings": [ + { + "id": "building type", + "freeform": { + "key": "building" + }, + "render": "The building type is {building}", + "mappings": [ + { + "if": "building=house", + "then": "A normal house" + }, + { + "if": "building=detached", + "then": "A house detached from other building" + }, + { + "if": "building=semidetached_house", + "then": "A house sharing only one wall with another house" + }, + { + "if": "building=apartments", + "then": "An apartment building - highrise for living" + }, + { + "if": "building=office", + "then": "An office building - highrise for work" + }, + { + "if": "building=apartments", + "then": "An apartment building" + }, + { + "if": "building=shed", + "then": "A small shed, e.g. in a garden" + }, + { + "if": "building=garage", + "then": "A single garage to park a car" + }, + { + "if": "building=garages", + "then": "A building containing only garages; typically they are all identical" + }, + { + "if": "building=yes", + "then": "A building - no specification" + } + ] + }, + "all_tags" + ] + }, + { + "id": "All OSM objects", + "name": "All OSM Objects", + "source": { + "osmTags": { + "and": [ + "id~*", + "landuse=", + "place=", + "disused:power=", + "power=", + "type!=boundary", + "boundary=", + { + "or": [ + "level=", + "level=0" + ] + }, + { + "or": [ + "layer=0", + "layer=" + ] + } + ] + }, + "maxCacheAge": 0 + }, + "mapRendering": [ + { + "color": { + "render": "#00c" + }, + "width": { + "render": "1" + } + } + ], + "title": { + "render": { + "*": "OSM-Object" + } + }, + "tagRenderings": [ + "all_tags" + ] + }, + { + "id": "osm-fixmes", + "name": { + "nl": "Fixmes op gebouwen" + }, + "source": { + "maxCacheAge": 0, + "osmTags": { + "and": [ + "fixme~*", + "building~*" + ] + } + }, + "calculatedTags": [ + "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]" + ], + "title": { + "render": { + "nl": "{addr:street} {addr:housenumber}" + }, + "mappings": [ + { + "if": { + "and": [ + "fixme~*" + ] + }, + "then": { + "nl": "{fixme}" + } + } + ] + }, + "description": { + "nl": "Dit gebouw heeft een foutmelding" + }, + "tagRenderings": [ + { + "id": "grb-housenumber", + "render": { + "nl": "Het huisnummer is {addr:housenumber}" + }, + "question": { + "nl": "Wat is het huisnummer?" + }, + "freeform": { + "key": "addr:housenumber" + }, + "mappings": [ + { + "if": { + "and": [ + "not:addr:housenumber=yes", + "addr:housenumber=" + ] + }, + "then": { + "nl": "Geen huisnummer" + } + }, + { + "if": { + "and": [ + "addr:housenumber:={_grbNumber}", + "fixme=" + ] + }, + "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB", + "hideInAnswer": { + "or": [ + "_grbNumber=", + "_grbNumber=none", + "_grbNumber=no number" + ] + } + }, + { + "if": { + "and": [ + "addr:housenumber=", + "not:addr:housenumber=yes", + "fixme=" + ] + }, + "then": "Dit gebouw heeft geen nummer, net zoals in het GRB", + "hideInAnswer": "_grbNumber!=no number" + } + ] + }, + { + "id": "grb-unit", + "question": "Wat is de wooneenheid-aanduiding?", + "render": { + "nl": "De wooneenheid-aanduiding is {addr:unit} " + }, + "freeform": { + "key": "addr:unit" + }, + "mappings": [ + { + "if": "addr:unit=", + "then": "Geen wooneenheid-nummer" + } + ] + }, + { + "id": "grb-street", + "render": { + "nl": "De straat is {addr:street}" + }, + "freeform": { + "key": "addr:street" + }, + "question": { + "nl": "Wat is de straat?" + } + }, + { + "id": "grb-fixme", + "render": { + "nl": "De fixme is {fixme}" + }, + "question": { + "nl": "Wat zegt de fixme?" + }, + "freeform": { + "key": "fixme" + }, + "mappings": [ + { + "if": { + "and": [ + "fixme=" + ] + }, + "then": { + "nl": "Geen fixme" + } + } + ] + }, + { + "id": "grb-min-level", + "render": { + "nl": "Dit gebouw begint maar op de {building:min_level} verdieping" + }, + "question": { + "nl": "Hoeveel verdiepingen ontbreken?" + }, + "freeform": { + "key": "building:min_level", + "type": "pnat" + } + } + ], + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "label": { + "mappings": [ + { + "if": "addr:housenumber~*", + "then": "
{addr:housenumber}
" + } + ] + }, + "iconSize": { + "render": "40,40,center" + } + }, + { + "dashes": "2 2", + "color": { + "render": "#00f" + }, + "width": { + "render": "2" + } + } + ] + }, + { + "id": "crab-addresses 2021-10-26", + "source": { + "osmTags": "HUISNR~*", + "geoJson": "https://raw.githubusercontent.com/pietervdvn/pietervdvn.github.io/master/CRAB_2021_10_26/tile_{z}_{x}_{y}.geojson", + "#geoJson": "https://pietervdvn.github.io/CRAB_2021_10_26/tile_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 18, + "maxCacheAge": 0 + }, + "name": "CRAB-addressen", + "title": "CRAB-adres", + "mapRendering": [ + { + "location": [ + "point" + ], + "icon": "circle:#bb3322", + "iconSize": "15,15,center" + } + ], + "calculatedTags": [ + "_embedded_in=feat.overlapWith('OSM-buildings').filter(f => f.feat.properties['addr:housenumber'] !== undefined)[0]?.feat?.properties ", + "_embedding_nr=feat.get('_embedded_in')['addr:housenumber']", + "_embedding_street=feat.get('_embedded_in')['addr:street']" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": { + "and": [ + "_embedding_nr:={HUISNR}", + "_embedding_street:={STRAATNM}" + ] + }, + "then": "no" + } + ] + }, + "tagRenderings": [ + { + "id": "render_crab", + "render": "Volgens het CRAB ligt hier {STRAATNM} {HUISNR} (label: {HNRLABEL})" + }, + { + "id": "render_embedded", + "render": "Het omliggende object met addres heeft {_embedding_street} {_embedding_nr}", + "condition": { + "and": [ + "_embedding_street~*", + "_embedding_nr~*" + ] + } + }, + "all_tags", + { + "id": "import-button", + "render": "{import_button(OSM-buildings, addr:street=$STRAATNM; addr:housenumber=$HUISNR,Import this address,,,OSM-buildings,5)}" + } + ] + }, + { + "id": "grb-fixmes", + "name": { + "nl": "Fixmes op gebouwen" + }, + "source": { + "maxCacheAge": 0, + "osmTags": { + "and": [ + "fixme~*", + "building~*" + ] + } + }, + "calculatedTags": [ + "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]" + ], + "title": { + "render": { + "nl": "{addr:street} {addr:housenumber}" + }, + "mappings": [ + { + "if": { + "and": [ + "fixme~*" + ] + }, + "then": { + "nl": "{fixme}" + } + } + ] + }, + "description": { + "nl": "Dit gebouw heeft een foutmelding" + }, + "tagRenderings": [ + { + "id": "grb-housenumber", + "render": { + "nl": "Het huisnummer is {addr:housenumber}" + }, + "question": { + "nl": "Wat is het huisnummer?" + }, + "freeform": { + "key": "addr:housenumber" + }, + "mappings": [ + { + "if": { + "and": [ + "not:addr:housenumber=yes", + "addr:housenumber=" + ] + }, + "then": { + "nl": "Geen huisnummer" + } + }, + { + "if": { + "and": [ + "addr:housenumber:={_grbNumber}", + "fixme=" + ] + }, + "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB", + "hideInAnswer": { + "or": [ + "_grbNumber=", + "_grbNumber=none", + "_grbNumber=no number" + ] + } + }, + { + "if": { + "and": [ + "addr:housenumber=", + "not:addr:housenumber=yes", + "fixme=" + ] + }, + "then": "Dit gebouw heeft geen nummer, net zoals in het GRB", + "hideInAnswer": "_grbNumber!=no number" + } + ] + }, + { + "id": "grb-unit", + "question": "Wat is de wooneenheid-aanduiding?", + "render": { + "nl": "De wooneenheid-aanduiding is {addr:unit} " + }, + "freeform": { + "key": "addr:unit" + }, + "mappings": [ + { + "if": "addr:unit=", + "then": "Geen wooneenheid-nummer" + } + ] + }, + { + "id": "grb-street", + "render": { + "nl": "De straat is {addr:street}" + }, + "freeform": { + "key": "addr:street" + }, + "question": { + "nl": "Wat is de straat?" + } + }, + { + "id": "grb-fixme", + "render": { + "nl": "De fixme is {fixme}" + }, + "question": { + "nl": "Wat zegt de fixme?" + }, + "freeform": { + "key": "fixme" + }, + "mappings": [ + { + "if": { + "and": [ + "fixme=" + ] + }, + "then": { + "nl": "Geen fixme" + } + } + ] + }, + { + "id": "grb-min-level", + "render": { + "nl": "Dit gebouw begint maar op de {building:min_level} verdieping" + }, + "question": { + "nl": "Hoeveel verdiepingen ontbreken?" + }, + "freeform": { + "key": "building:min_level", + "type": "pnat" + } + } + ], + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "iconSize": { + "render": "40,40,center" + }, + "label": { + "mappings": [ + { + "if": "addr:housenumber~*", + "then": "
{addr:housenumber}
" + } + ] + } + }, + { + "width": { + "render": "2" + }, + "color": { + "render": "#00f" + } + } + ] + }, + { + "id": "GRB", + "source": { + "osmTags": "HUISNR~*", + "geoJson": "https://betadata.grbosm.site/grb?bbox={x_min},{y_min},{x_max},{y_max}", + "geoJsonZoomLevel": 18, + "mercatorCrs": true, + "maxCacheAge": 0 + }, + "name": "GRB geometries", + "title": "GRB outline", + "calculatedTags": [ + "_overlaps_with=feat.overlapWith('OSM-buildings').filter(f => f.overlap > 1 && (feat.get('_surface') < 20 || f.overlap / feat.get('_surface')) > 0.9)[0] ?? null", + "_overlap_absolute=feat.get('_overlaps_with')?.overlap", + "_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_surface')) ", + "_osm_obj:source:ref=feat.get('_overlaps_with')?.feat?.properties['source:geometry:ref']", + "_osm_obj:source:date=feat.get('_overlaps_with')?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')", + "_osm_obj:building=feat.get('_overlaps_with')?.feat?.properties.building", + "_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties.id", + "_grb_ref=feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']", + "_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties._grb_ref", + "_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')", + "_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date" + ], + "tagRenderings": [ + { + "id": "Building info", + "render": "This is a {building} detected by {detection_method}" + }, + { + "id": "overlapping building type", + "render": "
The overlapping openstreetmap-building is a {_osm_obj:building} and covers {_overlap_percentage}% of the GRB building

GRB geometry:

{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}

OSM geometry:

{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}", + "condition": "_overlaps_with!=null" + }, + { + "id": "apply-id", + "render": "{tag_apply(source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref,Mark the OSM-building as imported,,_osm_obj:id)}", + "condition": { + "and": [ + "_overlaps_with!=null" + ] + } + }, + { + "id": "apply-building-type", + "render": "{tag_apply(building=$building,Use the building type from GRB,,_osm_obj:id)}", + "condition": { + "and": [ + "_overlaps_with!=null", + "_osm_obj:building=yes", + "building!=yes" + ] + } + }, + { + "id": "Import-button", + "render": "{import_button(OSM-buildings,building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Upload this building to OpenStreetMap)}", + "mappings": [ + { + "if": "_overlaps_with!=null", + "then": "{import_button(OSM-buildings,building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,,_osm_obj:id)}" + } + ] + }, + "all_tags" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": { + "and": [ + "_imported_osm_object_found=true", + "_imported_osm_still_fresh=true" + ] + }, + "then": "no" + } + ] + }, + "mapRendering": [ + { + "color": { + "render": "#00a", + "mappings": [ + { + "if": { + "and": [ + "_imported_osm_object_found=true", + "_imported_osm_still_fresh=true" + ] + }, + "then": "#0f0" + } + ] + } + } + ] + } + ], + "hideFromOverview": true, + "defaultBackgroundId": "AGIVFlandersGRB", + "overpassMaxZoom": 15, + "osmApiTileSize": 17 +} \ No newline at end of file diff --git a/assets/themes/hackerspaces/hackerspaces.json b/assets/themes/hackerspaces/hackerspaces.json index b2fc74c3a..7cad69310 100644 --- a/assets/themes/hackerspaces/hackerspaces.json +++ b/assets/themes/hackerspaces/hackerspaces.json @@ -233,7 +233,42 @@ "leisure=hackerspace" ] } - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hackerspaces/glider.svg", + "mappings": [ + { + "if": { + "and": [ + "hackerspace=makerspace" + ] + }, + "then": { + "en": "./assets/themes/hackerspaces/led.png", + "de": "./assets/themes/hackerspaces/led.png" + } + } + ] + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] } ] } \ No newline at end of file diff --git a/assets/themes/hailhydrant/hailhydrant.json b/assets/themes/hailhydrant/hailhydrant.json index a46b53d12..9b79f8440 100644 --- a/assets/themes/hailhydrant/hailhydrant.json +++ b/assets/themes/hailhydrant/hailhydrant.json @@ -340,7 +340,29 @@ } } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hailhydrant/hydrant.svg" + }, + "iconSize": { + "render": "20,20,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] }, { "id": "extinguisher", @@ -466,7 +488,20 @@ } } ], - "wayHandling": 1 + "wayHandling": 1, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hailhydrant/Twemoji12_1f9ef.svg" + }, + "iconSize": { + "render": "20,20,center" + }, + "location": [ + "point" + ] + } + ] }, { "id": "fire_stations", @@ -495,8 +530,7 @@ "ru": "Пожарная часть", "nb_NO": "Brannstasjon", "it": "Caserma dei vigili del fuoco", - "fr": "Station de pompiers", - "de": "Feuerwache" + "fr": "Station de pompiers" } }, "description": { @@ -696,6 +730,28 @@ "de": "Eine Feuerwache ist ein Ort, an dem die Feuerwehrfahrzeuge und die Feuerwehrleute untergebracht sind, wenn sie nicht im Einsatz sind." } } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hailhydrant/Twemoji12_1f692.svg" + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#c22" + }, + "width": { + "render": "1" + } + } ] }, { @@ -898,7 +954,29 @@ } } ], - "wayHandling": 2 + "wayHandling": 2, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hailhydrant/Twemoji_1f691.svg" + }, + "iconSize": { + "render": "35,35,center" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "1" + } + } + ] } ], "defaultBackgroundId": "HDM_HOT" diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index 78866a968..6441dcf3a 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -35,9 +35,9 @@ "enablePdfDownload": true, "enableDownload": true, "hideFromOverview": true, - "#": "Disable clustering for this theme", "clustering": { - "maxZoom": 0 + "maxZoom": 0, + "#": "Disable clustering for this theme" }, "layers": [ { @@ -57,9 +57,13 @@ }, "minzoom": 13, "minzoomVisible": 0, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" + } + } + ] } }, { @@ -77,9 +81,13 @@ "isOsmCache": "duplicate" }, "minzoom": 1, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - }, + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" + } + } + ], "presets": [] } }, @@ -96,9 +104,13 @@ "isOsmCache": true }, "minzoom": 1, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" + } + } + ] } }, { @@ -115,19 +127,23 @@ "isOsmCache": true }, "minzoom": 10, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" - }, - { - "if": "pushchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" + }, + { + "if": "pushchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" + } + ] } - ] - } + } + ] } }, { @@ -139,19 +155,23 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" - }, - { - "if": "toilets:position=urinals", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" + }, + { + "if": "toilets:position=urinals", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" + } + ] } - ] - } + } + ] } }, { @@ -163,10 +183,14 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", - "mappings": null - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", + "mappings": null + } + } + ] } }, { @@ -178,9 +202,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" + } + } + ] } }, { @@ -192,34 +220,42 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" + } + } + ] } }, { "builtin": "parking", "override": { "minzoom": "16", - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", - "mappings": [ - { - "if": "amenity=bicycle_parking", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" - } - ] - }, - "iconOverlays": [ + "mapRendering": [ { - "if": "amenity=motorcycle_parking", - "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg", - "badge": true - }, - { - "if": "capacity:disabled=yes", - "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", - "badge": true + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", + "mappings": [ + { + "if": "amenity=bicycle_parking", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" + } + ] + }, + "iconOverlays": [ + { + "if": "amenity=motorcycle_parking", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg", + "badge": true + }, + { + "if": "capacity:disabled=yes", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", + "badge": true + } + ] } ] } @@ -233,9 +269,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" + } + } + ] } }, { @@ -247,9 +287,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" + } + } + ] } }, { @@ -261,9 +305,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" + } + } + ] } } ], diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 04282bcae..394f28418 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -31,8 +31,7 @@ "name": { "en": "wind turbine", "nl": "windturbine", - "fr": "Éolienne", - "de": "Windrad" + "fr": "Éolienne" }, "source": { "osmTags": "generator:source=wind" @@ -43,8 +42,7 @@ "render": { "en": "wind turbine", "nl": "windturbine", - "fr": "éolienne", - "de": "Windrad" + "fr": "éolienne" }, "mappings": [ { @@ -152,8 +150,7 @@ "title": { "en": "wind turbine", "nl": "windturbine", - "fr": "Éolienne", - "de": "Windrad" + "fr": "Éolienne" } } ], @@ -172,8 +169,7 @@ "human": { "en": " megawatts", "nl": " megawatt", - "fr": " megawatts", - "de": " Megawatt" + "fr": " megawatts" } }, { @@ -185,8 +181,7 @@ "human": { "en": " kilowatts", "nl": " kilowatt", - "fr": " kilowatts", - "de": " Kilowatt" + "fr": " kilowatts" } }, { @@ -198,8 +193,7 @@ "human": { "en": " watts", "nl": " watt", - "fr": " watts", - "de": " Watt" + "fr": " watts" } }, { @@ -211,8 +205,7 @@ "human": { "en": " gigawatts", "nl": " gigawatt", - "fr": " gigawatts", - "de": " Gigawatt" + "fr": " gigawatts" } } ], @@ -232,12 +225,28 @@ "human": { "en": " meter", "nl": " meter", - "fr": " mètres", - "de": " Meter" + "fr": " mètres" } } ] } + ], + "mapRendering": [ + { + "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", + "label": { + "mappings": [ + { + "if": "generator:output:electricity~^[0-9]+.*[W]$", + "then": "
{generator:output:electricity}
" + } + ] + }, + "iconSize": "40, 40, bottom", + "location": [ + "point" + ] + } ] } ], diff --git a/assets/themes/postboxes/postboxes.json b/assets/themes/postboxes/postboxes.json index a6a30a372..7741ac2d6 100644 --- a/assets/themes/postboxes/postboxes.json +++ b/assets/themes/postboxes/postboxes.json @@ -31,8 +31,7 @@ { "id": "postboxes", "name": { - "en": "Postboxes", - "de": "Brieflästen" + "en": "Postboxes" }, "minzoom": 12, "source": { @@ -40,19 +39,17 @@ }, "title": { "render": { - "en": "Postbox", - "de": "Briefkasten" + "en": "Postbox" } }, "description": { - "en": "The layer showing postboxes.", - "de": "Die Ebene zeigt Briefkästen." + "en": "The layer showing postboxes." }, "tagRenderings": [ "images", { - "id": "minimap", - "render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }" + + "render": "{minimap(17): height: 10rem; overflow: hidden; border: 1px solid #DADADA; border-radius: 0.5rem; }" } ], "icon": { @@ -73,8 +70,7 @@ "amenity=post_box" ], "title": { - "en": "postbox", - "de": "Briefkasten" + "en": "postbox" } } ], @@ -86,13 +82,34 @@ "razed:amenity=post_box" ] } - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/postboxes/postbox.svg" + }, + "iconSize": { + "render": "40,40,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#DADADA" + }, + "width": { + "render": "1" + } + } + ] }, { "id": "postoffices", "name": { - "en": "Post offices", - "de": "Poststellen" + "en": "Post offices" }, "minzoom": 12, "source": { @@ -100,8 +117,7 @@ }, "title": { "render": { - "en": "Post Office", - "de": "Poststelle" + "en": "Post Office" } }, "description": { @@ -112,7 +128,9 @@ "images", { "id": "minimap", - "render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }" + + + "render": "{minimap(17): height: 10rem; overflow: hidden; border: 1px solid #DADADA; border-radius: 0.5rem; }" }, { "render": { @@ -143,8 +161,7 @@ "iconOverlays": [ { "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "then": "isOpen" } ], "width": { @@ -162,8 +179,7 @@ "amenity=post_office" ], "title": { - "en": "Post Office", - "de": "Poststelle" + "en": "Post Office" } } ], @@ -174,14 +190,41 @@ "options": [ { "question": { - "en": "Currently open", - "de": "Aktuell geöffnet" + "en": "Currently open" }, "osmTags": "_isOpen=yes" } ] } + ], + "mapRendering": [ + { + "icon": { + "render": "square:white;./assets/themes/postboxes/post_office.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,bottom" + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#DADADA" + }, + "width": { + "render": "1" + } + } ] } ] -} \ No newline at end of file +} diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json new file mode 100644 index 000000000..540cdb57a --- /dev/null +++ b/assets/themes/sidewalks/sidewalks.json @@ -0,0 +1,180 @@ +{ + "id": "sidewalks", + "title": { + "en": "Sidewalks" + }, + "shortDescription": { + "en": "Sidewalk mapping" + }, + "description": { + "en": "Experimental theme" + }, + "language": [ + "en" + ], + "maintainer": "", + "icon": "./assets/svg/bug.svg", + "version": "0", + "startLat": 0, + "startLon": 0, + "startZoom": 1, + "widenFactor": 0.05, + "socialImage": "", + "hideFromOverview": true, + "layers": [ + { + "id": "sidewalks", + "name": { + "en": "Sidewalks" + }, + "minzoom": 12, + "source": { + "osmTags": { + "or": [ + "highway=residential", + "highway=tertiary", + "highway=secondary" + ] + } + }, + "title": { + "render": { + "en": "{name}" + }, + "mappings": [ + { + "if": "name=", + "then": "Nameless street" + } + ] + }, + "description": { + "en": "Layer showing sidewalks of highways" + }, + "tagRenderings": [ + { + "id": "streetname", + "render": { + "en": "This street is named {name}" + } + }, + { + "rewrite": { + "sourceString": "left|right", + "into": [ + "left", + "right" + ] + }, + "renderings": [ + { + "id": "sidewalk_minimap", + "render": "{sided_minimap(left|right):height:8rem;border-radius:0.5rem;overflow:hidden}" + }, + { + "id": "has_sidewalk", + "question": "Is there a sidewalk on this side of the road?", + "mappings": [ + { + "if": "sidewalk:left|right=yes", + "then": "Yes, there is a sidewalk on this side of the road" + }, + { + "if": "sidewalk:left|right=no", + "then": "No, there is no seperated sidewalk to walk on" + } + ] + }, + { + "id": "sidewalk_width", + "question": "What is the width of the sidewalk on this side of the road?", + "render": "This sidewalk is {sidewalk:left|right:width}m wide", + "condition": "sidewalk:left|right=yes", + "freeform": { + "key": "sidewalk:left|right:width", + "type": "length", + "helperArgs": [ + "21", + "map" + ] + } + } + ] + } + ], + "mapRendering": [ + { + "location": [ + "start", + "end" + ], + "icon": "circle:#ccc", + "iconSize": "20,20,center" + }, + { + "#": "The center line", + "color": "#ffffff55", + "width": 8 + }, + { + "#": "left", + "color": { + "render": "#888" + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": "sidewalk:left=", + "then": "1,12" + } + ] + }, + "width": { + "render": 6, + "mappings": [ + { + "if": { + "or": [ + "sidewalk:left=no", + "sidewalk:left=separate" + ] + }, + "then": 0 + } + ] + }, + "offset": -6 + }, + { + "color": "#888", + "dashArray": { + "render": "", + "mappings": [ + { + "if": "sidewalk:right=", + "then": "1,12" + } + ] + }, + "width": { + "render": 6, + "mappings": [ + { + "if": { + "or": [ + "sidewalk:right=no", + "sidewalk:right=separate" + ] + }, + "then": 0 + } + ] + }, + "offset": 6 + } + ], + "allowSplit": true + } + ] +} \ No newline at end of file diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index d25d24cf9..175f7685b 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -36,7 +36,20 @@ "color": "#444444", "width": { "render": "1" - } + }, + "mapRendering": [ + { + "location": [ + "point" + ] + }, + { + "color": "#444444", + "width": { + "render": "1" + } + } + ] }, { "builtin": "play_forest", @@ -252,7 +265,33 @@ }, "width": { "render": "9" - } + }, + "mapRendering": [ + { + "icon": "./assets/themes/speelplekken/walking_route.svg", + "location": [ + "point" + ] + }, + { + "color": { + "render": "#6d6", + "mappings": [ + { + "if": "color~*", + "then": "{color}" + }, + { + "if": "colour~*", + "then": "{colour}" + } + ] + }, + "width": { + "render": "9" + } + } + ] } ], "clustering": { diff --git a/assets/themes/uk_addresses/Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg b/assets/themes/uk_addresses/Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg new file mode 100644 index 000000000..a51b9a826 Binary files /dev/null and b/assets/themes/uk_addresses/Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg differ diff --git a/assets/themes/uk_addresses/Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg b/assets/themes/uk_addresses/Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg new file mode 100644 index 000000000..c38517431 Binary files /dev/null and b/assets/themes/uk_addresses/Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg differ diff --git a/assets/themes/uk_addresses/Plaque,_Séamus_Roddy_House_-_geograph.org.uk_-_2000318.jpg b/assets/themes/uk_addresses/Plaque,_Séamus_Roddy_House_-_geograph.org.uk_-_2000318.jpg new file mode 100644 index 000000000..c4c68b90b Binary files /dev/null and b/assets/themes/uk_addresses/Plaque,_Séamus_Roddy_House_-_geograph.org.uk_-_2000318.jpg differ diff --git a/assets/themes/uk_addresses/license_info.json b/assets/themes/uk_addresses/license_info.json index 7d805cee4..4bcce8003 100644 --- a/assets/themes/uk_addresses/license_info.json +++ b/assets/themes/uk_addresses/license_info.json @@ -1,4 +1,34 @@ [ + { + "path": "Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg", + "license": "CC-BY-SA 2.0 Unported", + "authors": [ + "Basher Eyre" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg" + ] + }, + { + "path": "Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg", + "license": "CC-BY-SA 2.0", + "authors": [ + "Kenneth Allen" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg" + ] + }, + { + "path": "Plaque,_Séamus_Roddy_House_-_geograph.org.uk_-_2000318.jpg", + "license": "CC-BY-SA 2.0 Unported", + "authors": [ + "Kenneth Allen" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Plaque,_S%C3%A9amus_Roddy_House_-_geograph.org.uk_-_2000318.jpg" + ] + }, { "path": "housenumber_add.svg", "license": "CC0", diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index aa0aeed0b..a9f9d0a48 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -57,23 +57,27 @@ }, "name": "Addresses to check", "minzoom": 14, - "wayHandling": 1, - "icon": { - "render": "./assets/themes/uk_addresses/housenumber_unknown.svg", - "mappings": [ - { - "if": "_embedding_object:id~*", - "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg" + "mapRendering": [ + { + "location": "point", + "icon": { + "render": "./assets/themes/uk_addresses/housenumber_unknown.svg", + "mappings": [ + { + "if": "_embedding_object:id~*", + "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg" + }, + { + "if": "_imported=yes", + "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg" + } + ] }, - { - "if": "_imported=yes", - "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg" + "iconSize": { + "render": "40,40,center" } - ] - }, - "iconSize": { - "render": "40,40,center" - }, + } + ], "title": { "render": "Address to be determined" }, @@ -89,7 +93,7 @@ }, { "id": "uk_addresses_import_button", - "render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}" + "render": "{import_button(addresses, ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}" } ], "calculatedTags": [ @@ -225,50 +229,57 @@ } } ], - "icon": { - "render": "./assets/themes/uk_addresses/housenumber_ok.svg", - "mappings": [ - { - "if": { - "or": [ - { - "and": [ - "addr:housenumber=", - "nohousenumber!=yes" + "mapRendering": [ + { + "location": "point", + "icon": { + "render": "./assets/themes/uk_addresses/housenumber_ok.svg", + "mappings": [ + { + "if": { + "or": [ + { + "and": [ + "addr:housenumber=", + "nohousenumber!=yes" + ] + }, + "addr:street=" ] }, - "addr:street=" - ] - }, - "then": "./assets/themes/uk_addresses/housenumber_unknown.svg" + "then": "./assets/themes/uk_addresses/housenumber_unknown.svg" + } + ] + }, + "iconSize": { + "render": "40,40,center" } - ] - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f", - "mappings": [ - { - "if": { - "or": [ - { - "and": [ - "addr:housenumber=", - "nohousenumber!=yes" + }, + { + "color": { + "render": "#00f", + "mappings": [ + { + "if": { + "or": [ + { + "and": [ + "addr:housenumber=", + "nohousenumber!=yes" + ] + }, + "addr:street=" ] }, - "addr:street=" - ] - }, - "then": "#ff0" + "then": "#ff0" + } + ] + }, + "width": { + "render": "8" } - ] - } + } + ] }, { "id": "named_streets", @@ -281,12 +292,9 @@ ] } }, - "color": { - "render": "#ccc" - }, - "width": { - "render": "0" - } + "mapRendering": [] } - ] + ], + "enableShareScreen": false, + "enableMoreQuests": false } \ No newline at end of file diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index ee61e9cea..658e97769 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -748,6 +748,14 @@ video { right: 0.75rem; } +.bottom-0 { + bottom: 0px; +} + +.right-1\/3 { + right: 33.333333%; +} + .top-0 { top: 0px; } @@ -760,10 +768,6 @@ video { right: 0px; } -.bottom-0 { - bottom: 0px; -} - .isolate { isolation: isolate; } @@ -856,10 +860,22 @@ video { margin-left: 0.75rem; } +.mt-1 { + margin-top: 0.25rem; +} + .mr-4 { margin-right: 1rem; } +.mt-4 { + margin-top: 1rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + .mt-3 { margin-top: 0.75rem; } @@ -880,26 +896,14 @@ video { margin-bottom: 2.5rem; } -.mt-1 { - margin-top: 0.25rem; -} - .mt-0 { margin-top: 0px; } -.ml-2 { - margin-left: 0.5rem; -} - .mb-4 { margin-bottom: 1rem; } -.mt-4 { - margin-top: 1rem; -} - .mb-8 { margin-bottom: 2rem; } @@ -988,6 +992,10 @@ video { height: 2rem; } +.h-1\/2 { + height: 50%; +} + .h-12 { height: 3rem; } @@ -1044,6 +1052,10 @@ video { width: 2rem; } +.w-0 { + width: 0px; +} + .w-12 { width: 3rem; } @@ -1056,10 +1068,6 @@ video { width: 2.75rem; } -.w-0 { - width: 0px; -} - .w-16 { width: 4rem; } @@ -1409,14 +1417,6 @@ video { padding-right: 0.25rem; } -.pt-6 { - padding-top: 1.5rem; -} - -.pb-3 { - padding-bottom: 0.75rem; -} - .pl-5 { padding-left: 1.25rem; } @@ -1441,10 +1441,6 @@ video { padding-right: 0px; } -.pb-2 { - padding-bottom: 0.5rem; -} - .pt-0\.5 { padding-top: 0.125rem; } @@ -1453,6 +1449,10 @@ video { padding-top: 0px; } +.pb-2 { + padding-bottom: 0.5rem; +} + .pr-2 { padding-right: 0.5rem; } @@ -1585,14 +1585,14 @@ video { text-decoration: underline; } -.opacity-0 { - opacity: 0; -} - .opacity-50 { opacity: 0.5; } +.opacity-0 { + opacity: 0; +} + .opacity-40 { opacity: 0.4; } @@ -1801,6 +1801,15 @@ html, body { display: block ruby; } +.badge { +} + +.badge svg { + /*Workaround for leaflet*/ + width: unset !important; + height: 100% !important; +} + svg, img { box-sizing: content-box; width: 100%; diff --git a/css/openinghourstable.css b/css/openinghourstable.css index b477b4862..877355426 100644 --- a/css/openinghourstable.css +++ b/css/openinghourstable.css @@ -16,6 +16,8 @@ border-collapse: collapse; background-clip: padding-box; border-right: 1px solid #ccc; + /* Somthing sets linehight to 1.5, but the leaflet-popup-content sets it to 1.4, throwing of the calculations... */ + line-height: 1.5 !important; } .oh-timecell { diff --git a/index.css b/index.css index 108f02fe2..d72c0a3e6 100644 --- a/index.css +++ b/index.css @@ -91,6 +91,15 @@ html, body { display: block ruby; } +.badge { +} + +.badge svg { + /*Workaround for leaflet*/ + width: unset !important; + height: 100% !important; +} + svg, img { box-sizing: content-box; width: 100%; diff --git a/index.ts b/index.ts index 92ba9da66..dcf030c48 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,6 @@ import {FixedUiElement} from "./UI/Base/FixedUiElement"; import {QueryParameters} from "./Logic/Web/QueryParameters"; import Combine from "./UI/Base/Combine"; -import ValidatedTextField from "./UI/Input/ValidatedTextField"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import MinimapImplementation from "./UI/Base/MinimapImplementation"; import CountryCoder from "latlon2country/index"; @@ -10,10 +9,11 @@ import {Utils} from "./Utils"; import AllThemesGui from "./UI/AllThemesGui"; import DetermineLayout from "./Logic/DetermineLayout"; import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; -import DefaultGUI, {DefaultGuiState} from "./UI/DefaultGUI"; +import DefaultGUI from "./UI/DefaultGUI"; import State from "./State"; import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; +import {DefaultGuiState} from "./UI/DefaultGuiState"; // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console MinimapImplementation.initialize() @@ -33,8 +33,6 @@ if (location.href.startsWith("http://buurtnatuur.be")) { class Init { - - public static Init(layoutToUse: LayoutConfig, encoded: string) { if(layoutToUse === null){ @@ -67,12 +65,13 @@ class Init { const guiState = new DefaultGuiState() State.state = new State(layoutToUse); + DefaultGuiState.state = guiState; // This 'leaks' the global state via the window object, useful for debugging // @ts-ignore window.mapcomplete_state = State.state; + new DefaultGUI(State.state, guiState) - if (encoded !== undefined && encoded.length > 10) { // We save the layout to the user settings and local storage State.state.osmConnection.OnLoggedIn(() => { @@ -80,13 +79,8 @@ class Init { .GetLongPreference("installed-theme-" + layoutToUse.id) .setData(encoded); }); - } - - } - - } diff --git a/langs/de.json b/langs/de.json index ae55b7bcc..808201619 100644 --- a/langs/de.json +++ b/langs/de.json @@ -160,9 +160,9 @@ "downloadCSVHelper": "Kompatibel mit LibreOffice Calc, Excel, …", "downloadCSV": "Sichtbare Daten als CSV herunterladen", "downloadAsPdfHelper": "Ideal zum Drucken der aktuellen Karte", - "downloadGeoJsonHelper": "Kompatibel mit QGIS, ArcGIS, ESRI, ...", + "downloadGeoJsonHelper": "Kompatibel mit QGIS, ArcGIS, ESRI, …", "downloadAsPdf": "PDF der aktuellen Karte herunterladen", - "downloadGeojson": "Sichtbare Daten als geojson herunterladen", + "downloadGeojson": "Sichtbare Daten als GeoJSON herunterladen", "includeMetaData": "Metadaten übernehmen (letzter Bearbeiter, berechnete Werte, ...)", "noDataLoaded": "Noch keine Daten geladen. Download ist in Kürze verfügbar", "licenseInfo": "

Copyright-Hinweis

Die bereitgestellten Daten sind unter ODbL verfügbar. Die Wiederverwendung dieser Daten ist für jeden Zweck frei, aber
  • die Namensnennung © OpenStreetMap contributors ist erforderlich
  • Jede Änderung dieser Daten muss unter der gleichen Lizenz veröffentlicht werden
Bitte lesen Sie den vollständigen Copyright-Hinweis für weitere Details" diff --git a/langs/en.json b/langs/en.json index cee2bae0c..ef1c347af 100644 --- a/langs/en.json +++ b/langs/en.json @@ -75,8 +75,8 @@ }, "loginWithOpenStreetMap": "Login with OpenStreetMap", "welcomeBack": "You are logged in, welcome back!", - "loginToStart": "Login to answer this question", - "openStreetMapIntro": "

An Open Map

Wouldn't it be cool if there was a single map, which everyone could freely use and edit? A single place to store all geo-information? Then, all those websites with different, small and incompatible maps (which are always outdated) wouldn't be needed anymore.

OpenStreetMap is this map. The map data can be used for free (with attribution and publication of changes to that data). On top of that, everyone can freely add new data and fix errors. This website uses OpenStreetMap as well. All the data is from there, and your answers and corrections are added there as well.

A ton of people and application already use OpenStreetMap: Organic Maps, OsmAnd, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap. If you change something here, it'll be reflected in those applications too - after their next update!

", + "loginToStart": "Log in to answer this question", + "openStreetMapIntro": "

An Open Map

One that everyone can use and edit freely. A single place to store all geo-info. Different, small, incompatible and outdated maps are not needed anywhere.

OpenStreetMap is not the enemy map. The map data can be used freely (with attribution and publication of changes to that data). Everyone can add new data and fix errors. This website uses OpenStreetMap. All the data is from there, and your answers and corrections are used all over.

Many people and apps already use OpenStreetMap: Organic Maps, OsmAnd, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap.

", "search": { "search": "Search a location", "searching": "Searching…", @@ -90,7 +90,7 @@ "oneSkippedQuestion": "One question is skipped", "skippedQuestions": "Some questions are skipped", "number": "number", - "osmLinkTooltip": "See this object on OpenStreetMap for history and more editing options", + "osmLinkTooltip": "Browse this object on OpenStreetMap for history and more editing options", "add": { "addNewMapLabel": "Add new item", "disableFiltersExplanation": "Some features might be hidden by a filter", @@ -108,7 +108,8 @@ "openLayerControl": "Open the layer control box", "layerNotEnabled": "The layer {layer} is not enabled. Enable this layer to add a point", "hasBeenImported": "This point has already been imported", - "zoomInMore": "Zoom in more to import this feature" + "zoomInMore": "Zoom in more to import this feature", + "wrongType": "This element is not a point or a way and can not be imported" }, "pickLanguage": "Choose a language: ", "about": "Easily edit and add OpenStreetMap for a certain theme", @@ -128,11 +129,11 @@ "streetcomplete": "Another, similar application is StreetComplete.", "createYourOwnTheme": "Create your own MapComplete theme from scratch", "previouslyHiddenTitle": "Previously visited hidden themes", - "hiddenExplanation": "These themes are only visible if you know the link. You have discovered {hidden_discovered} out of {total_hidden} hidden themes" + "hiddenExplanation": "These themes are only accessible to those with the link. You have discovered {hidden_discovered} of {total_hidden} hidden themes." }, "sharescreen": { "intro": "

Share this map

Share this map by copying the link below and sending it to friends and family:", - "addToHomeScreen": "

Add to your home screen

You can easily add this website to your smartphone home screen for a native feel. Click the 'add to home screen' button in the URL bar to do this.", + "addToHomeScreen": "

Add to your home screen

You can easily add this website to your smartphone home screen for a native feel. Click the 'Add to home screen' button in the URL bar to do this.", "embedIntro": "

Embed on your website

Please, embed this map into your website.
We encourage you to do it - you don't even have to ask permission.
It is free, and always will be. The more people are using this, the more valuable it becomes.", "copiedToClipboard": "Link copied to clipboard", "thanksForSharing": "Thanks for sharing!", @@ -158,17 +159,25 @@ }, "mapContributionsBy": "The current visible data has edits made by {contributors}", "mapContributionsByAndHidden": "The current visible data has edits made by {contributors} and {hiddenCount} more contributors", - "codeContributionsBy": "MapComplete has been built by {contributors} and {hiddenCount} more contributors" + "codeContributionsBy": "MapComplete has been built by {contributors} and {hiddenCount} more contributors", + "openOsmcha": "See latest edits made with {theme}", + "openMapillary": "Open Mapillary here", + "openIssueTracker": "File a bug", + "josmOpened": "JOSM is opened", + "josmNotOpened": "JOSM could not be reached. Make sure it is opened and remote control is enabled", + "editJosm": "Edit here with JOSM", + "editId": "Open the OpenStreetMap online editor here", + "donate": "Support MapComplete financially" }, "readYourMessages": "Please, read all your OpenStreetMap-messages before adding a new point.", "fewChangesBefore": "Please, answer a few questions of existing points before adding a new point.", "goToInbox": "Open inbox", - "getStartedLogin": "Login with OpenStreetMap to get started", + "getStartedLogin": "Log in with OpenStreetMap to get started", "getStartedNewAccount": " or create a new account", "noTagsSelected": "No tags selected", "testing": "Testing - changes won't be saved", "customThemeIntro": "

Custom themes

These are previously visited user-generated themes.", - "aboutMapcomplete": "

About MapComplete

With MapComplete you can enrich OpenStreetMap with information on a single theme. Answer a few questions, and within minutes your contributions will be available around the globe! The theme maintainer defines elements, questions and languages for the theme.

Find out more

MapComplete always offers the next step to learn more about OpenStreetMap.

  • When embedded in a website, the iframe links to a full-screen MapComplete
  • The full-screen version offers information about OpenStreetMap
  • Viewing works without login, but editing requires an OSM login.
  • If you are not logged in, you are asked to log in
  • Once you answered a single question, you can add new points to the map
  • After a while, actual OSM-tags are shown, later linking to the wiki


Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code or issue tracker.

Want to see your progress? Follow the edit count on OsmCha.

", + "aboutMapcomplete": "

About MapComplete

Use it to add OpenStreetMap info on a single theme. Answer questions, and within minutes your contributions are available everywhere. The theme maintainer defines elements, questions and languages for it.

Find out more

MapComplete always offers the next step to learn more about OpenStreetMap.

  • When embedded in a website, the iframe links to a full-screen MapComplete
  • The fullscreen version offers info about OpenStreetMap
  • Viewing works without login, but editing requires an OSM account.
  • If you are not logged in, you are asked to do so
  • Once you answered a single question, you can add new points to the map
  • After a while, actual OSM-tags are shown, later linking to the wiki


Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code or issue tracker.

Want to see your progress? Follow the edit count on OsmCha.

", "backgroundMap": "Background map", "openTheMap": "Open the map", "loginOnlyNeededToEdit": "if you want to edit the map", @@ -180,13 +189,13 @@ "title": "Download visible data", "downloadAsPdf": "Download a PDF of the current map", "downloadAsPdfHelper": "Ideal to print the current map", - "downloadGeojson": "Download visible data as geojson", - "exporting": "Exporting...", - "downloadGeoJsonHelper": "Compatible with QGIS, ArcGIS, ESRI, ...", + "downloadGeojson": "Download visible data as GeoJSON", + "exporting": "Exporting…", + "downloadGeoJsonHelper": "Compatible with QGIS, ArcGIS, ESRI, …", "downloadCSV": "Download visible data as CSV", "downloadCSVHelper": "Compatible with LibreOffice Calc, Excel, …", "includeMetaData": "Include metadata (last editor, calculated values, …)", - "licenseInfo": "

Copyright notice

The provided is available under ODbL. Reusing this data is free for any purpose, but
  • the attribution © OpenStreetMap contributors is required
  • Any change to this data must be republished under the same license
Please read the full copyright notice for details", + "licenseInfo": "

Copyright notice

The provided data is available under ODbL. Reusing it is gratis for any purpose, but
  • the attribution © OpenStreetMap contributors is required
  • Any change must be use the license
Please read the full copyright notice for details.", "noDataLoaded": "No data is loaded yet. Download will be available soon" }, "weekdays": { @@ -227,13 +236,17 @@ }, "wikipedia": { "wikipediaboxTitle": "Wikipedia", - "failed": "Loading the wikipedia entry failed", + "failed": "Loading the Wikipedia entry failed", "loading": "Loading Wikipedia...", "noWikipediaPage": "This Wikidata item has no corresponding Wikipedia page yet.", "searchWikidata": "Search on Wikidata", "noResults": "Nothing found for {search}", "doSearch": "Search above to see results", "createNewWikidata": "Create a new Wikidata item" + }, + "apply_button": { + "isApplied": "The changes are applied", + "appliedOnAnotherObject": "The object {id} will receive {tags}" } }, "favourite": { @@ -255,7 +268,7 @@ "saved": "Review saved. Thanks for sharing!", "tos": "If you create a review, you agree to the TOS and privacy policy of Mangrove.reviews", "attribution": "Reviews are powered by Mangrove Reviews and are available under CC-BY 4.0.", - "plz_login": "Login to leave a review" + "plz_login": "Log in to leave a review" }, "multi_apply": { "autoApply": "When changing the attributes {attr_names}, these attributes will automatically be changed on {count} other objects too" @@ -281,8 +294,8 @@ "cannotBeMoved": "This feature cannot be moved.", "isWay": "This feature is a way. Use another OpenStreetMap editor to move it.", "isRelation": "This feature is a relation and can not be moved", - "partOfAWay": "This feature is part of another way. Use another editor to move it", - "partOfRelation": "This feature is part of a relation. Use another editor to move it", + "partOfAWay": "This feature is part of another way. Use another editor to move it.", + "partOfRelation": "This feature is part of a relation. Use another editor to move it.", "cancel": "Cancel move" } -} +} \ No newline at end of file diff --git a/langs/it.json b/langs/it.json index d67165f51..dee1190af 100644 --- a/langs/it.json +++ b/langs/it.json @@ -1,175 +1,288 @@ { - "reviews": { - "attribution": "Le recensioni sono fornite da Mangrove Reviews e sono disponibili con licenza CC-BY 4.0.", - "tos": "Quando pubblichi una recensione, accetti i termini di utilizzo e la informativa sulla privacy di Mangrove.reviews", - "plz_login": "Accedi per lasciare una recensione", - "saved": "Recensione salvata. Grazie per averla condivisa!", - "saving_review": "Salvataggio…", - "affiliated_reviewer_warning": "(Recensione di un affiliato)", - "i_am_affiliated": "Sono associato con questo oggetto
Seleziona se sei il proprietario, creatore, dipendente, ...", - "posting_as": "Pubblica come", - "no_rating": "Nessun voto ricevuto", - "write_a_comment": "Lascia una recensione…", - "no_reviews_yet": "Non ci sono ancora recensioni. Sii il primo a scriverne una aiutando così i dati liberi e l’attività!", - "name_required": "È richiesto un nome per poter mostrare e creare recensioni", - "title_singular": "Una recensione", - "title": "{count} recensioni" - }, - "general": { - "aboutMapcomplete": "

Informazioni su MapComplete

Con MapComplete puoi arricchire OpenStreetMap con informazioni su un singolo argomento. Rispondi a poche domande e in pochi minuti i tuoi contributi saranno disponibili a tutto il mondo! L’utente gestore del tema definisce gli elementi, le domande e le lingue per quel tema.

Scopri altro

MapComplete propone sempre un passo in più per imparare qualcosa di nuovo su OpenStreetMap.

  • Quando viene incorporato in un sito web, il collegamento dell’iframe punta a MapComplete a tutto schermo
  • La versione a tutto schermo fornisce informazioni su OpenStreetMap
  • La visualizzazione non necessita di alcun accesso ma per modificare occorre aver effettuato l’accesso su OSM.
  • Se non hai effettuato l’accesso, ti verrà richiesto di farlo
  • Dopo aver risposto ad una sola domanda potrai aggiungere dei nuovi punti alla mappa
  • Dopo qualche momento verranno mostrate le etichette effettive, in seguito i collegamenti alla wiki


Hai trovato un errore? Vuoi richiedere nuove funzionalità? Vuoi aiutare con la traduzione? Dai un’occhiata al codice sorgente oppure al tracker degli errori.

Vuoi vedere i tuoi progressi?Segui il contatore delle modifiche su OsmCha.

", - "morescreen": { - "requestATheme": "Se hai bisogno di una mappa tematica personalizzata, puoi chiederla qua.", - "createYourOwnTheme": "Crea il tuo tema di MapComplete personalizzato da zero", - "streetcomplete": "Un’altra simile applicazione è StreetComplete.", - "intro": "

Altre mappe tematiche?

Ti diverti a raccogliere dati geografici?
Sono disponibili altri temi." + "reviews": { + "attribution": "Le recensioni sono fornite da Mangrove Reviews e sono disponibili con licenza CC-BY 4.0.", + "tos": "Quando pubblichi una recensione, accetti i termini di utilizzo e la informativa sulla privacy di Mangrove.reviews", + "plz_login": "Accedi per lasciare una recensione", + "saved": "Recensione salvata. Grazie per averla condivisa!", + "saving_review": "Salvataggio…", + "affiliated_reviewer_warning": "(Recensione di un affiliato)", + "i_am_affiliated": "Sono associato con questo oggetto
Spunta se sei il proprietario, creatore, dipendente, etc.", + "posting_as": "Pubblica come", + "no_rating": "Nessun voto ricevuto", + "write_a_comment": "Lascia una recensione…", + "no_reviews_yet": "Non ci sono ancora recensioni. Sii il primo a scriverne una aiutando così i dati liberi e l’attività!", + "name_required": "È richiesto un nome per poter mostrare e creare recensioni", + "title_singular": "Una recensione", + "title": "{count} recensioni" }, - "sharescreen": { - "embedIntro": "

Incorpora nel tuo sito web

Per favore, incorpora questa mappa nel tuo sito web.
Ti incoraggiamo a farlo (non devi neanche chiederci il permesso!).
È gratis e lo sarà per sempre. Più persone lo useranno e più valore acquisirà.", - "addToHomeScreen": "

Aggiungi alla tua schermata Home

Puoi aggiungere facilmente questo sito web alla schermata Home del tuo smartphone. Per farlo, clicca sul pulsante ‘Aggiungi a schermata Home’ nella barra degli indirizzi.", - "fsIncludeCurrentLocation": "Includi la posizione attuale", - "fsIncludeCurrentBackgroundMap": "Includi lo sfondo attualmente selezionato {name}", - "fsIncludeCurrentLayers": "Includi i livelli correntemente selezionati", - "fsGeolocation": "Abilita il pusante ‘geo-localizzami’ (solo da mobile)", - "fsAddNew": "Abilita il pulsante ‘aggiungi nuovo PDI’", - "fsLayerControlToggle": "Inizia con il pannello dei livelli aperto", - "fsLayers": "Abilita il controllo dei livelli", - "fsWelcomeMessage": "Mostra il messaggio di benvenuto e le schede associate", - "fsSearch": "Abilita la barra di ricerca", - "fsUserbadge": "Abilita il pulsante di accesso", - "editThemeDescription": "Aggiungi o modifica le domande a questo tema della mappa", - "editThisTheme": "Modifica questo tema", - "thanksForSharing": "Grazie per la condivisione!", - "copiedToClipboard": "Collegamento copiato negli appunti", - "intro": "

Condividi questa mappa

Condividi questa mappa copiando il collegamento qua sotto e inviandolo ad amici o parenti:" + "general": { + "aboutMapcomplete": "

Informazioni su MapComplete

Con MapComplete puoi arricchire OpenStreetMap con informazioni su un singolo argomento. Rispondi a poche domande e in pochi minuti i tuoi contributi saranno disponibili a tutto il mondo! L’utente gestore del tema definisce gli elementi, le domande e le lingue per quel tema.

Scopri altro

MapComplete propone sempre un passo in più per imparare qualcosa di nuovo su OpenStreetMap.

  • Quando viene incorporato in un sito web, il collegamento dell’iframe punta a MapComplete a tutto schermo
  • La versione a tutto schermo fornisce informazioni su OpenStreetMap
  • La visualizzazione non necessita di alcun accesso ma per modificare occorre aver effettuato l’accesso su OSM.
  • Se non hai effettuato l’accesso, ti verrà richiesto di farlo
  • Dopo aver risposto ad una sola domanda potrai aggiungere dei nuovi punti alla mappa
  • Dopo qualche momento verranno mostrate le etichette effettive, in seguito i collegamenti alla wiki


Hai trovato un errore? Vuoi richiedere nuove funzionalità? Vuoi aiutare con la traduzione? Dai un’occhiata al codice sorgente oppure al tracker degli errori.

Vuoi vedere i tuoi progressi?Segui il contatore delle modifiche su OsmCha.

", + "morescreen": { + "requestATheme": "Se hai bisogno di una mappa tematica personalizzata, puoi chiederla nel tracker degli errori", + "createYourOwnTheme": "Crea il tuo tema di MapComplete personalizzato da zero", + "streetcomplete": "Un’altra simile applicazione è StreetComplete.", + "intro": "

Altre mappe tematiche?

Ti diverti a raccogliere dati geografici?
Sono disponibili altri temi.", + "previouslyHiddenTitle": "Temi nascosti precedentemente visitati", + "hiddenExplanation": "Questi temi sono solo accessibili se si dispone del collegamento. Hai scoperto {hidden_discovered} su {total_hidden} temi nascosti." + }, + "sharescreen": { + "embedIntro": "

Incorpora nel tuo sito web

Siamo lieti se vorrai includere questa cartina nel tuo sito web.
Ti incoraggiamo a farlo (non devi neanche chieder il permesso).
È gratuito e lo sarà per sempre. Più persone lo useranno e più valore acquisirà.", + "addToHomeScreen": "

Aggiungi alla tua schermata Home

Puoi aggiungere facilmente questo sito web alla schermata Home del tuo smartphone. Per farlo, clicca sul pulsante ‘Aggiungi a schermata Home’ nella barra degli indirizzi.", + "fsIncludeCurrentLocation": "Includi la posizione attuale", + "fsIncludeCurrentBackgroundMap": "Includi lo sfondo attualmente selezionato {name}", + "fsIncludeCurrentLayers": "Includi i livelli correntemente selezionati", + "fsGeolocation": "Abilita il pusante ‘geo-localizzami’ (solo da mobile)", + "fsAddNew": "Abilita il pulsante ‘aggiungi nuovo PDI’", + "fsLayerControlToggle": "Inizia con il pannello dei livelli aperto", + "fsLayers": "Abilita il controllo dei livelli", + "fsWelcomeMessage": "Mostra il messaggio di benvenuto e le schede associate", + "fsSearch": "Abilita la barra di ricerca", + "fsUserbadge": "Abilita il pulsante di accesso", + "editThemeDescription": "Aggiungi o modifica le domande a questo tema della mappa", + "editThisTheme": "Modifica questo tema", + "thanksForSharing": "Grazie per la condivisione!", + "copiedToClipboard": "Collegamento copiato negli appunti", + "intro": "

Condividi questa mappa

Condividi questa mappa copiando il collegamento qua sotto e inviandolo ad amici o parenti:" + }, + "attribution": { + "attributionContent": "

Tutti i dati sono forniti da OpenStreetMap, riutilizzabili liberamente con Open Database License

", + "attributionTitle": "Crediti", + "codeContributionsBy": "MapComplete è stato realizzato da {contributors} e {hiddenCount} altri collaboratori", + "mapContributionsByAndHidden": "I dati attualmente visibili sono stati modificati da {contributors} e {hiddenCount} altri contributori", + "mapContributionsBy": "I dati attualmente visibili sono stati creati da {contributors}", + "iconAttribution": { + "title": "Icone utilizzate" + }, + "themeBy": "Tema manutenuto da {author}" + }, + "openStreetMapIntro": "

Una mappa libera

Non sarebbe perfetto se esistesse una carta geografica che chiunque può modificare e utilizzare liberamente? Un unico posto in un cui conservare tutte le informazioni geografiche? In questo modo tutti questi siti web con mappe diverse, piccole e incompatibili (che sono sempre obsolete) diverrebbero istantaneamente inutili.

OpenStreetMap è proprio questa mappa. I dati geografici possono essere usati liberamente (rispettando l’attribuzione e la pubblicazione delle modifiche di quei dati). In più, chiunque può aggiungere liberamente nuovi dati e correggere gli errori. Anche questo sito usa OpenStreetMap. Tutti i dati provengono da lì e le tue risposte e correzioni finiscono sempre lì.

Moltissime persone e applicazioni già usano OpenStreetmap: Maps.me, OsmAnd ma anche le cartine di Facebook, Instagram, Apple e Bing si basano (parzialmente) su OpenStreetMap. Tutto quello che cambi qua si rifletterà anche su quelle applicazioni (non appena avranno aggiornato i loro dati!)

", + "opening_hours": { + "ph_open": "aperto", + "ph_closed": "chiuso", + "ph_not_known": " ", + "open_24_7": "Sempre aperto", + "closed_permanently": "Chiuso per un periodo sconosciuto", + "closed_until": "Chiuso fino al {date}", + "not_all_rules_parsed": "Gli orari di apertura di questo negozio sono complicati. Le seguenti regole sono state ignorate per l’oggetto in ingresso:", + "openTill": "fino a", + "opensAt": "da", + "open_during_ph": "Durante le festività questo luogo è", + "error_loading": "Errore: impossibile visualizzare questi orari di apertura.", + "ph_open_as_usual": "aperto come di consueto", + "loadingCountry": "Determinazione del Paese…" + }, + "weekdays": { + "sunday": "Domenica", + "saturday": "Sabato", + "friday": "Venerdì", + "thursday": "Giovedì", + "wednesday": "Mercoledì", + "tuesday": "Martedì", + "monday": "Lunedì", + "abbreviations": { + "sunday": "Dom", + "saturday": "Sab", + "friday": "Ven", + "thursday": "Gio", + "wednesday": "Mer", + "tuesday": "Mar", + "monday": "Lun" + } + }, + "layerSelection": { + "title": "Seleziona livelli", + "zoomInToSeeThisLayer": "Ingrandisci la mappa per vedere questo livello" + }, + "backgroundMap": "Mappa di sfondo", + "customThemeIntro": "

Temi personalizzati

Questi sono i temi degli utenti che hai già visitato.", + "noTagsSelected": "Nessuna etichetta selezionata", + "getStartedNewAccount": " oppure crea un nuovo account", + "getStartedLogin": "Accedi con OpenStreetMap per iniziare", + "goToInbox": "Apri posta in arrivo", + "fewChangesBefore": "Rispondi ad alcune domande di punti esistenti prima di aggiungere un nuovo punto.", + "readYourMessages": "Leggi tutti i tuoi messaggi OpenStreetMap prima di aggiungere un nuovo punto.", + "questions": { + "emailIs": "L’indirizzo email di questa {category} è {email}", + "emailOf": "Qual è l’indirizzo email di {category}?", + "websiteIs": "Sito web: {website}", + "websiteOf": "Qual è il sito web di {category}?", + "phoneNumberIs": "Il numero di telefono di questa {category} è {phone}", + "phoneNumberOf": "Qual è il numero di telefono di {category}?" + }, + "noNameCategory": "{category} senza nome", + "nameInlineQuestion": "Il nome di questa {category} è $$$", + "about": "Modifica e aggiungi con semplicità OpenStreetMap per un certo tema", + "pickLanguage": "Scegli una lingua: ", + "add": { + "layerNotEnabled": "Il livello {layer} non è abilitato. Abilita questo livello per aggiungere un punto", + "openLayerControl": "Apri il pannello di controllo dei livelli", + "confirmButton": "Aggiungi una {category} qua.
La tua aggiunta è visibile a chiunque
", + "confirmIntro": "

Aggiungere un {title} qua?

Il punto che hai creato qua sarà visibile da chiunque. Per favore, aggiungi sulla mappa solo oggetti realmente esistenti. Molte applicazioni usano questi dati.", + "stillLoading": "Caricamento dei dati ancora in corso. Attendi un po’ prima di aggiungere un nuovo punto.", + "zoomInFurther": "Ingrandisci la mappa per aggiungere un punto.", + "pleaseLogin": "Accedi per aggiungere un punto", + "intro": "Hai cliccato in un punto dove non ci sono ancora dei dati.
", + "title": "Aggiungi un nuovo punto?", + "addNew": "Aggiungi una nuova {category} qua", + "presetInfo": "Il nuovo PDI avrà {tags}", + "warnVisibleForEveryone": "La tua aggiunta sarà visibile a tutti", + "zoomInMore": "Ingrandisci ancora per importare questo oggetto", + "hasBeenImported": "Questo punto è stato già importato", + "disableFilters": "Disabilita tutti i filtri", + "addNewMapLabel": "Aggiungi nuovo elemento", + "disableFiltersExplanation": "Alcuni oggetti potrebbero essere nascosti da un filtro" + }, + "osmLinkTooltip": "Visita questo oggetto su OpenStreetMap per la cronologia o altre opzioni di modifica", + "number": "numero", + "skippedQuestions": "Alcune domande sono state scartate", + "oneSkippedQuestion": "Una domanda è stata scartata", + "skip": "Salta questa domanda", + "cancel": "Annulla", + "save": "Salva", + "returnToTheMap": "Ritorna alla mappa", + "search": { + "error": "Qualcosa è andato storto…", + "nothing": "Non è stato trovato nulla…", + "searching": "Ricerca…", + "search": "Cerca un luogo" + }, + "loginToStart": "Accedi per rispondere alla domanda", + "welcomeBack": "Hai effettuato l’accesso. Bentornato/a!", + "loginWithOpenStreetMap": "Accedi con OpenStreetMap", + "loading": "Caricamento…", + "download": { + "downloadAsPdf": "Scarica un PDF della mappa corrente", + "downloadCSV": "Scarica i dati visibili come CSV", + "noDataLoaded": "Nessun dato è stato ancora caricato. Lo scaricamento sarà disponibile a breve", + "downloadGeojson": "Scarica i dati visibili come GeoJSON", + "downloadAsPdfHelper": "Ideale per stampare la mappa corrente", + "downloadGeoJsonHelper": "Compatibile con QGIS, ArcGIS, ESRI, etc.", + "title": "Scarica i dati visibili", + "downloadCSVHelper": "Compatibile con LibreOffice Calc, Excel, etc.", + "includeMetaData": "Includi metadati (ultimo utente, valori calcolati, etc.)", + "licenseInfo": "

Informativa sul copyright

I dati forniti sono disponibili con licenza ODbL. Il riutilizzo di tali dati è libero per qualsiasi scopo ma
  • è richiesta l’attribuzione © OpenStreetMap contributors
  • qualsiasi modifica di questi data deve essere rilasciata con la stessa licenza
Per ulteriori dettagli si prega di leggere l’informativa completa sul copyright", + "exporting": "Esportazione in corso…" + }, + "testing": "Prova (le modifiche non verranno salvate)", + "pdf": { + "versionInfo": "v{version} - generato il {date}", + "attr": "Dati della mappa © OpenStreetMap Contributors, riutilizzabile con licenza ODbL", + "generatedWith": "Generato con MapComplete.osm.be", + "attrBackground": "Livello di sfondo: {background}" + }, + "openTheMap": "Apri la mappa", + "histogram": { + "error_loading": "Impossibile caricare l'istogramma" + }, + "wikipedia": { + "loading": "Caricamento Wikipedia…", + "noResults": "Nessun elemento trovato per {search}", + "doSearch": "Cerca qui sopra per vedere i risultati", + "noWikipediaPage": "Questo elemento Wikidata non ha ancora una pagina Wikipedia corrispondente.", + "searchWikidata": "Cerca su Wikidata", + "createNewWikidata": "Crea un nuovo elemento Wikidata", + "wikipediaboxTitle": "Wikipedia", + "failed": "Caricamento della voce Wikipedia fallito" + }, + "loginOnlyNeededToEdit": "se vuoi modificare la mappa" }, - "attribution": { - "attributionContent": "

Tutti i dati sono forniti da OpenStreetMap, riutilizzabili liberamente con Open Database License

", - "attributionTitle": "Crediti", - "codeContributionsBy": "MapComplete è stato realizzato da {contributors} e {hiddenCount} altri collaboratori", - "mapContributionsByAndHidden": "I dati attualmente visibili sono stati modificati da {contributors} e {hiddenCount} altri contributori", - "mapContributionsBy": "I dati attualmente visibili sono stati creati da {contributors}", - "iconAttribution": { - "title": "Icone utilizzate" - }, - "themeBy": "Tema manutenuto da {author}" + "index": { + "#": "Questi testi sono mostrati sopra ai pulsanti del tema quando nessun tema è stato caricato", + "pickTheme": "Scegli un tema qui sotto per iniziare.", + "intro": "MapComplete è un visualizzatore/editore di OpenStreetMap che mostra le informazioni riguardanti gli oggetti di uno specifico tema e permette di aggiornarle.", + "title": "Benvenuto/a su MapComplete", + "featuredThemeTitle": "Questa settimana in vetrina" }, - "openStreetMapIntro": "

Una mappa libera

Non sarebbe perfetto se esistesse una carta geografica che chiunque può modificare e utilizzare liberamente? Un unico posto in un cui conservare tutte le informazioni geografiche? In questo modo tutti questi siti web con mappe diverse, piccole e incompatibili (che sono sempre obsolete) diverrebbero istantaneamente inutili.

OpenStreetMap è proprio questa mappa. I dati geografici possono essere usati liberamente (rispettando l’attribuzione e la pubblicazione delle modifiche di quei dati). In più, chiunque può aggiungere liberamente nuovi dati e correggere gli errori. Anche questo sito usa OpenStreetMap. Tutti i dati provengono da lì e le tue risposte e correzioni finiscono sempre lì.

Moltissime persone e applicazioni già usano OpenStreetmap: Maps.me, OsmAnd ma anche le cartine di Facebook, Instagram, Apple e Bing si basano (parzialmente) su OpenStreetMap. Tutto quello che cambi qua si rifletterà anche su quelle applicazioni (non appena avranno aggiornato i loro dati!)

", - "opening_hours": { - "ph_open": "aperto", - "ph_closed": "chiuso", - "ph_not_known": " ", - "open_24_7": "Sempre aperto", - "closed_permanently": "Chiuso per un periodo sconosciuto", - "closed_until": "Chiuso fino al {date}", - "not_all_rules_parsed": "Gli orari di apertura di questo negozio sono complicati. Le seguenti regole sono state ignorate per l’oggetto in ingresso:", - "openTill": "fino a", - "opensAt": "da", - "open_during_ph": "Durante le festività questo luogo è", - "error_loading": "Errore: impossibile visualizzare questi orari di apertura." + "favourite": { + "reload": "Ricarica i dati", + "loginNeeded": "

Accedi

Il layout personale è disponibile soltanto per gli utenti OpenStreetMap", + "panelIntro": "

Il tuo tema personale

Attiva i tuoi livelli preferiti fra tutti i temi ufficiali" }, - "weekdays": { - "sunday": "Domenica", - "saturday": "Sabato", - "friday": "Venerdì", - "thursday": "Giovedì", - "wednesday": "Mercoledì", - "tuesday": "Martedì", - "monday": "Lunedì", - "abbreviations": { - "sunday": "Dom", - "saturday": "Sab", - "friday": "Ven", - "thursday": "Gio", - "wednesday": "Mer", - "tuesday": "Mar", - "monday": "Lun" - } + "centerMessage": { + "retrying": "Caricamento dei dati fallito. Nuovo tentativo tra {count} secondi…", + "ready": "Finito!", + "zoomIn": "Ingrandisci la mappa per vedere e modificare i dati", + "loadingData": "Caricamento dei dati…" }, - "layerSelection": { - "title": "Seleziona livelli", - "zoomInToSeeThisLayer": "Ingrandisci la mappa per vedere questo livello" + "image": { + "isDeleted": "Cancellata", + "doDelete": "Rimuovi immagine", + "dontDelete": "Annulla", + "uploadDone": "La tua foto è stata aggiunta. Grazie per l’aiuto!", + "respectPrivacy": "Non fotografare persone o targhe dei veicoli. Non caricare da Google Maps, Google Streetview o da altre fonti coperte da copyright.", + "uploadFailed": "Impossibile caricare la tua foto. La connessione internet è attiva e le API di terze parti sono abilitate? Il browser Brave o il plugin uMatrix potrebbero bloccarle.", + "ccb": "con licenza CC-BY", + "ccbs": "con licenza CC-BY-SA", + "cco": "nel pubblico dominio", + "willBePublished": "La tua foto sarà pubblicata: ", + "pleaseLogin": "Accedi per caricare una foto", + "uploadingMultiple": "Caricamento di {count} foto…", + "uploadingPicture": "Caricamento della tua foto…", + "addPicture": "Aggiungi foto", + "uploadMultipleDone": "Sono state aggiunte {count} immagini. Grazie per l’aiuto!", + "toBig": "La tua immagine è troppo grande in quanto è di {actual_size}. Cerca di usare immagini non più grandi di {max_size}" }, - "backgroundMap": "Mappa di sfondo", - "customThemeIntro": "

Temi personalizzati

Questi sono i temi degli utenti che hai già visitato.", - "noTagsSelected": "Nessuna etichetta selezionata", - "getStartedNewAccount": " oppure crea un nuovo account", - "getStartedLogin": "Accedi con OpenStreetMap per iniziare", - "goToInbox": "Apri posta in arrivo", - "fewChangesBefore": "Rispondi ad alcune domande di punti esistenti prima di aggiungere un nuovo punto.", - "readYourMessages": "Leggi tutti i tuoi messaggi OpenStreetMap prima di aggiungere un nuovo punto.", - "questions": { - "emailIs": "L’indirizzo email di questa {category} è {email}", - "emailOf": "Qual è l’indirizzo email di {category}?", - "websiteIs": "Sito web: {website}", - "websiteOf": "Qual è il sito web di {category}?", - "phoneNumberIs": "Il numero di telefono di questa {category} è {phone}", - "phoneNumberOf": "Qual è il numero di telefono di {category}?" + "delete": { + "reasons": { + "test": "Si tratta di un punto di prova (l’oggetto non è mai esistito in quel punto)", + "disused": "L’oggetto è in disuso o è stato smantellato", + "notFound": "Non è stato possibile trovare l’oggetto", + "duplicate": "Questo punto è un duplicato di un altro oggetto" + }, + "explanations": { + "selectReason": "Si prega di selezionare il motivo della rimozione di questo oggetto", + "hardDelete": "Questo punto verrà rimosso da OpenStreetMap. Un utente esperto potrebbe recuperarlo", + "softDelete": "Questo oggetto verrà aggiornato e nascosto da questa applicazione. {reason}" + }, + "loginToDelete": "Devi aver effettuato l’accesso per poter rimuovere un punto", + "safeDelete": "Questo punto può essere rimosso in sicurezza.", + "isntAPoint": "Solo i punti possono essere rimossi, l’oggetto selezionato è un percorso, un’area oppure una relazione.", + "onlyEditedByLoggedInUser": "Questo punto è stato modificato soltanto da te, puoi rimuoverlo in sicurezza.", + "notEnoughExperience": "Questo nodo è stato creato da un altro utente.", + "delete": "Rimuovi", + "isDeleted": "Questo oggetto è stato rimosso", + "cannotBeDeleted": "Questo oggetto non può essere rimosso", + "useSomethingElse": "Per rimuoverlo usa un altro editor OpenStreetMap", + "loading": "Controllo delle proprietà per verificare se questo oggetto può essere rimosso.", + "partOfOthers": "Questo punto fa parte di qualche percorso o relazione e non può essere rimosso direttamente.", + "whyDelete": "Perché questo nodo andrebbe rimosso?", + "cancel": "Annulla", + "readMessages": "Hai dei messaggi non letti. Leggili prima di rimuovere un punto (qualcuno potrebbe aver lasciato un commento)" }, - "noNameCategory": "{category} senza nome", - "nameInlineQuestion": "Il nome di questa {category} è $$$", - "about": "Modifica e aggiungi con semplicità OpenStreetMap per un certo tema", - "pickLanguage": "Scegli una lingua: ", - "add": { - "layerNotEnabled": "Il livello {layer} non è abilitato. Abilita questo livello per aggiungere un punto", - "openLayerControl": "Apri il pannello di controllo dei livelli", - "confirmButton": "Aggiungi una {category} qua.
La tua aggiunta è visibile a chiunque
", - "confirmIntro": "

Aggiungere un {title} qua?

Il punto che hai creato qua sarà visibile da chiunque. Per favore, aggiungi sulla mappa solo oggetti realmente esistenti. Molte applicazioni usano questi dati.", - "stillLoading": "Caricamento dei dati ancora in corso. Attendi un po’ prima di aggiungere un nuovo punto.", - "zoomInFurther": "Ingrandisci la mappa per aggiungere un punto.", - "pleaseLogin": "Accedi per aggiungere un punto", - "intro": "Hai cliccato in un punto dove non ci sono ancora dei dati.
", - "title": "Aggiungi un nuovo punto?", - "addNew": "Aggiungi una nuova {category} qua" + "move": { + "loginToMove": "Devi aver effettuato l’accesso per spostare un punto", + "partOfAWay": "Quest’oggetto fa parte di un altro percorso. Usa un altro editor per spostarlo.", + "partOfRelation": "Quest’oggetto fa parte di una relazione. Usa un altro editor per spostarlo.", + "isWay": "Quest’oggetto è un percorso. Usa un altro editor OpenStreetMap per spostarlo.", + "isRelation": "Quest’oggetto è una relazione e non può essere spostato", + "cancel": "Annulla lo spostamento", + "pointIsMoved": "Questo punto è stato spostato", + "zoomInFurther": "Ingrandisci ulteriormente per confermare questo spostamento", + "moveTitle": "Sposta questo punto", + "whyMove": "Perché vuoi spostare questo punto?", + "confirmMove": "Spostalo qua", + "inviteToMove": { + "reasonInaccurate": "Migliora la precisione di questo punto", + "generic": "Sposta questo punto", + "reasonRelocation": "Sposta quest’oggetto in un altro luogo perché è stato ricollocato" + }, + "selectReason": "Perché vuoi spostare quest’oggetto?", + "reasons": { + "reasonInaccurate": "La posizione di questo oggetto non è precisa e dovrebbe essere spostato di alcuni metri", + "reasonRelocation": "Questo oggetto è stato ricollocato in una posizione totalmente differente" + }, + "inviteToMoveAgain": "Sposta di nuovo questo punto", + "cannotBeMoved": "Questo oggetto non può essere spostato." }, - "osmLinkTooltip": "Vedi questo oggetto su OpenStreetMap per la cronologia o altre opzioni di modifica", - "number": "numero", - "skippedQuestions": "Alcune domande sono state scartate", - "oneSkippedQuestion": "Una domanda è stata scartata", - "skip": "Salta questa domanda", - "cancel": "Annulla", - "save": "Salva", - "returnToTheMap": "Ritorna alla mappa", - "search": { - "error": "Qualcosa è andato storto…", - "nothing": "Non è stato trovato nulla…", - "searching": "Ricerca…", - "search": "Cerca un luogo" + "multi_apply": { + "autoApply": "Quando si modificano gli attributi {attr_names}, questi attributi vengono anche automaticamente cambiati su altri {count} oggetti" }, - "loginToStart": "Accedi per rispondere alla domanda", - "welcomeBack": "Hai effettuato l’accesso. Bentornato/a!", - "loginWithOpenStreetMap": "Accedi con OpenStreetMap" - }, - "index": { - "#": "Questi testi sono mostrati sopra ai pulsanti del tema quando nessun tema è stato caricato", - "pickTheme": "Scegli un tema qui sotto per iniziare.", - "intro": "MapComplete è un visualizzatore/editore di OpenStreetMap che mostra le informazioni riguardanti uno specifico tema.", - "title": "Benvenuto/a su MapComplete" - }, - "favourite": { - "reload": "Ricarica i dati", - "loginNeeded": "

Accedi

Il layout personale è disponibile soltanto per gli utenti OpenStreetMap", - "panelIntro": "

Il tuo tema personale

Attiva i tuoi livelli preferiti fra tutti i temi ufficiali" - }, - "centerMessage": { - "retrying": "Caricamento dei dati fallito. Nuovo tentativo tra {count} secondi…", - "ready": "Finito!", - "zoomIn": "Ingrandisci la mappa per vedere e modificare i dati", - "loadingData": "Caricamento dei dati…" - }, - "image": { - "isDeleted": "Cancellata", - "doDelete": "Rimuovi immagine", - "dontDelete": "Annulla", - "uploadDone": "La tua foto è stata aggiunta! Grazie mille!", - "respectPrivacy": "Non fotografare persone o targhe dei veicoli. Non caricare da Google Maps, Google Streetview o da altre fonti coperte da copyright.", - "uploadFailed": "Impossibile caricare la tua foto. La connessione internet è attiva e le API di terze parti sono abilitate? Il browser Brave o il plugin uMatrix potrebbero bloccarle.", - "ccb": "con licenza CC-BY", - "ccbs": "con licenza CC-BY-SA", - "cco": "nel pubblico dominio", - "willBePublished": "La tua foto sarà pubblicata: ", - "pleaseLogin": "Accedi per caricare una foto", - "uploadingMultiple": "Caricamento di {count} foto…", - "uploadingPicture": "Caricamento della tua foto…", - "addPicture": "Aggiungi foto" - } + "split": { + "hasBeenSplit": "Questa strada è stata divisa", + "cancel": "Annulla", + "splitTitle": "Scegli sulla cartina il punto dove vuoi dividere la strada", + "inviteToSplit": "Dividi questa strada in segmenti più piccoli. Ciò permette di assegnare proprietà differenti a ciascun pezzo di strada.", + "split": "Dividi", + "loginToSplit": "Devi aver effettuato l’accesso per dividere una strada" + } } diff --git a/langs/layers/de.json b/langs/layers/de.json index 46ea03b70..3917d519b 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -918,7 +918,6 @@ } }, "charging_station": { - "description": "Eine Ladestation", "filter": { "0": { "options": { @@ -954,7 +953,6 @@ } } }, - "name": "Ladestationen", "presets": { "0": { "title": "Ladestation" @@ -1188,9 +1186,6 @@ "render": "Weitere Informationen auf {website}" } }, - "title": { - "render": "Ladestation" - }, "units": { "0": { "applicableUnits": { @@ -1252,39 +1247,6 @@ }, "question": "Können Radfahrer diese Kreuzung nutzen?" }, - "crossing-button": { - "mappings": { - "1": { - "then": "Diese Ampel hat keine Taste, um ein grünes Signal anzufordern." - } - }, - "question": "Hat diese Ampel eine Taste, um ein grünes Signal anzufordern?" - }, - "crossing-continue-through-red": { - "mappings": { - "0": { - "then": "Ein Radfahrer kann bei roter Ampel geradeaus fahren " - }, - "1": { - "then": "Ein Radfahrer kann bei roter Ampel geradeaus fahren" - }, - "2": { - "then": "Ein Radfahrer kann bei roter Ampel nicht geradeaus fahren" - } - }, - "question": "Kann ein Radfahrer bei roter Ampel geradeaus fahren?" - }, - "crossing-has-island": { - "mappings": { - "0": { - "then": "Der Übergang hat eine Verkehrsinsel" - }, - "1": { - "then": "Diese Ampel hat eine Taste, um ein grünes Signal anzufordern" - } - }, - "question": "Gibt es an diesem Übergang eine Verkehrsinsel?" - }, "crossing-is-zebra": { "mappings": { "0": { @@ -1296,20 +1258,6 @@ }, "question": "Ist das ein Zebrastreifen?" }, - "crossing-right-turn-through-red": { - "mappings": { - "0": { - "then": "Ein Radfahrer kann bei roter Ampel rechts abbiegen " - }, - "1": { - "then": "Ein Radfahrer kann bei roter Ampel rechts abbiegen" - }, - "2": { - "then": "Ein Radfahrer kann bei roter Ampel nicht rechts abbiegen" - } - }, - "question": "Kann ein Radfahrer bei roter Ampel rechts abbiegen?" - }, "crossing-tactile": { "mappings": { "0": { @@ -1689,6 +1637,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Defibrillatoren", "presets": { "0": { @@ -1726,7 +1681,6 @@ "then": "Dies ist ein normaler automatischer Defibrillator" } }, - "question": "Ist dies ein normaler automatischer Defibrillator oder ein manueller Defibrillator nur für Profis?", "render": "Es gibt keine Informationen über den Gerätetyp" }, "defibrillator-defibrillator:location": { @@ -1815,7 +1769,7 @@ "name": "Trinkwasserstelle", "presets": { "0": { - "title": "Trinkwasserstelle" + "title": "trinkwasser" } }, "tagRenderings": { @@ -1832,9 +1786,6 @@ }, "Still in use?": { "mappings": { - "0": { - "then": "Diese Trinkwasserstelle funktioniert" - }, "1": { "then": "Diese Trinkwasserstelle ist kaputt" }, @@ -1846,7 +1797,7 @@ "render": "Der Betriebsstatus ist {operational_status" }, "render-closest-drinking-water": { - "render": "Eine weitere Trinkwasserstelle liegt {_closest_other_drinking_water_distance} Meter entfernt" + "render": "Ein weiterer Trinkwasserbrunnen befindet sich in {_closest_other_drinking_water_distance} Meter" } }, "title": { @@ -1857,17 +1808,7 @@ "description": "Alle Objekte, die eine bekannte Namensherkunft haben", "tagRenderings": { "simple etymology": { - "mappings": { - "0": { - "then": "Der Ursprung dieses Namens ist in der gesamten Literatur unbekannt" - } - }, - "question": "Wonach ist dieses Objekt benannt?
Das könnte auf einem Straßenschild stehen", "render": "Benannt nach {name:etymology}" - }, - "wikipedia-etymology": { - "question": "Was ist das Wikidata-Element, nach dem dieses Objekt benannt ist?", - "render": "

Wikipedia Artikel zur Namensherkunft

{wikipedia(name:etymology:wikidata):max-height:20rem}" } } }, @@ -1880,32 +1821,17 @@ } } }, - "1": { - "options": { - "0": { - "question": "Hat vegetarische Speisen" - } - } - }, "2": { "options": { "0": { "question": "Bietet vegan Speisen an" } } - }, - "3": { - "options": { - "0": { - "question": "Hat halal Speisen" - } - } } }, "name": "Restaurants und Fast Food", "presets": { "0": { - "description": "Ein klassisches Speiselokal mit Sitzgelegenheiten, in dem vollständige Mahlzeiten von Kellnern serviert werden", "title": "Restaurant" }, "1": { @@ -1918,12 +1844,6 @@ "tagRenderings": { "Cuisine": { "mappings": { - "0": { - "then": "Dies ist eine Pizzeria" - }, - "1": { - "then": "Dies ist eine Pommesbude" - }, "2": { "then": "Bietet vorwiegend Pastagerichte an" } @@ -1952,17 +1872,6 @@ }, "question": "Ist an diesem Ort Mitnahme möglich?" }, - "Vegetarian (no friture)": { - "question": "Gibt es im das Restaurant vegetarische Speisen?" - }, - "friture-take-your-container": { - "mappings": { - "0": { - "then": "Sie können ihre eigenen Behälter mitbringen, um Ihre Bestellung zu erhalten, was Einwegverpackungsmaterial und damit Abfall spart" - } - }, - "question": "Wenn Sie Ihr eigenes Behältnis mitbringen (z. B. einen Kochtopf und kleine Töpfe), wird es dann zum Verpacken Ihrer Bestellung verwendet?
" - }, "halal (no friture)": { "mappings": { "0": { @@ -1977,8 +1886,7 @@ "3": { "then": "Es gibt ausschließlich halal Speisen" } - }, - "question": "Gibt es im das Restaurant halal Speisen?" + } } }, "title": { @@ -2001,7 +1909,7 @@ }, "tagRenderings": { "ghost-bike-explanation": { - "render": "Ein Geisterrad ist ein weißes Fahrrad, dass zum Gedenken eines tödlich verunglückten Radfahrers vor Ort aufgestellt wurde." + "render": "Ein Geisterrad ist ein Denkmal für einen Radfahrer, der bei einem Verkehrsunfall ums Leben kam, in Form eines weißen Fahrrades, das dauerhaft in der Nähe des Unfallortes aufgestellt wird." }, "ghost_bike-inscription": { "question": "Wie lautet die Inschrift auf diesem Geisterrad?", @@ -2021,8 +1929,7 @@ "render": "Mehr Informationen" }, "ghost_bike-start_date": { - "question": "Wann wurde dieses Geisterrad aufgestellt?", - "render": "Aufgestellt am {start_date}" + "question": "Wann wurde dieses Geisterrad aufgestellt?" } }, "title": { @@ -2091,9 +1998,6 @@ }, "nature_reserve": { "tagRenderings": { - "Curator": { - "question": "Wer ist der Verwalter dieses Naturschutzgebietes?
Respektieren Sie die Privatsphäre - geben Sie nur dann einen Namen an, wenn dieser allgemein bekannt ist" - }, "Dogs?": { "mappings": { "0": { @@ -2111,9 +2015,6 @@ "Email": { "render": "{email}" }, - "Surface area": { - "render": "Grundfläche: {_surface:ha}ha" - }, "Website": { "question": "Auf welcher Webseite kann man mehr Informationen über dieses Naturschutzgebiet finden?" }, @@ -2137,7 +2038,6 @@ "then": "Eintritt kostenlos" } }, - "question": "Was kostet der Zugang zu diesem Turm?", "render": "Der Besuch des Turms kostet {charge}" }, "Height": { @@ -2261,11 +2161,9 @@ "question": "Ist dieser Spielplatz nachts beleuchtet?" }, "playground-max_age": { - "question": "Bis zu welchem Alter dürfen Kinder auf diesem Spielplatz spielen?", "render": "Zugang nur für Kinder bis maximal {max_age}" }, "playground-min_age": { - "question": "Ab welchem Alter dürfen Kinder auf diesem Spielplatz spielen?", "render": "Zugang nur für Kinder ab {min_age} Jahren" }, "playground-opening_hours": { @@ -2287,7 +2185,6 @@ "render": "Betrieben von {operator}" }, "playground-phone": { - "question": "Wie lautet die Telefonnummer vom Betreiber des Spielplatzes?", "render": "{phone}" }, "playground-surface": { @@ -2448,22 +2345,11 @@ "name": "Geschäft", "presets": { "0": { - "description": "Ein neues Geschäft hinzufügen", - "title": "Geschäft" + "description": "Ein neues Geschäft hinzufügen" } }, "tagRenderings": { - "shops-email": { - "question": "Wie ist die Email-Adresse dieses Geschäfts?" - }, - "shops-name": { - "question": "Wie ist der Name dieses Geschäfts?" - }, - "shops-opening_hours": { - "question": "Wie sind die Öffnungszeiten dieses Geschäfts?" - }, "shops-phone": { - "question": "Wie ist die Telefonnummer?", "render": "{phone}" }, "shops-shop": { @@ -2510,39 +2396,6 @@ "render": "Geschäft" } }, - "slow_roads": { - "tagRenderings": { - "slow_roads-surface": { - "mappings": { - "0": { - "then": "Die Oberfläche ist Gras" - }, - "1": { - "then": "Die Oberfläche ist Erde" - }, - "2": { - "then": "Die Oberfläche ist ohne festen Belag" - }, - "3": { - "then": "Die Oberfläche ist Sand" - }, - "4": { - "then": "Die Oberfläche ist aus Pflastersteinen" - }, - "5": { - "then": "Die Oberfläche ist Asphalt" - }, - "6": { - "then": "Die Oberfläche ist Beton" - }, - "7": { - "then": "Die Oberfläche ist gepflastert" - } - }, - "render": "Die Oberfläche ist {surface}" - } - } - }, "sport_pitch": { "description": "Ein Sportplatz", "name": "Sportplätze", @@ -2560,9 +2413,6 @@ "0": { "then": "Öffentlicher Zugang" }, - "1": { - "then": "Eingeschränkter Zugang (z. B. nur mit Termin, zu bestimmten Zeiten, ...)" - }, "2": { "then": "Zugang nur für Vereinsmitglieder" }, @@ -2574,23 +2424,10 @@ }, "sport-pitch-reservation": { "mappings": { - "0": { - "then": "Für die Nutzung des Sportplatzes ist eine Voranmeldung erforderlich" - }, - "1": { - "then": "Für die Nutzung des Sportplatzes wird eine Voranmeldung empfohlen" - }, - "2": { - "then": "Eine Voranmeldung ist möglich, aber nicht notwendig, um diesen Sportplatz zu nutzen" - }, "3": { "then": "Termine nach Vereinbarung nicht möglich" } - }, - "question": "Muss man einen Termin vereinbaren, um diesen Sportplatz zu benutzen?" - }, - "sport_pitch-email": { - "question": "Wie ist die Email-Adresse des Betreibers?" + } }, "sport_pitch-opening_hours": { "mappings": { @@ -2600,9 +2437,6 @@ }, "question": "Wann ist dieser Sportplatz zugänglich?" }, - "sport_pitch-phone": { - "question": "Wie ist die Telefonnummer des Betreibers?" - }, "sport_pitch-sport": { "mappings": { "0": { @@ -2645,7 +2479,6 @@ "then": "Die Oberfläche ist Beton" } }, - "question": "Was ist die Oberfläche dieses Sportplatzes?", "render": "Die Oberfläche ist {surface}" } }, @@ -2681,8 +2514,7 @@ "2": { "then": "Diese Kamera ist möglicherweise im Freien" } - }, - "question": "Handelt es sich bei dem von dieser Kamera überwachten öffentlichen Raum um einen Innen- oder Außenbereich?" + } }, "Level": { "question": "Auf welcher Ebene befindet sich diese Kamera?", @@ -2692,17 +2524,6 @@ "question": "Wer betreibt diese CCTV Kamera?", "render": "Betrieben von {operator}" }, - "Surveillance type: public, outdoor, indoor": { - "mappings": { - "1": { - "then": "Ein privater Außenbereich wird überwacht (z. B. ein Parkplatz, eine Tankstelle, ein Innenhof, ein Eingang, eine private Einfahrt, ...)" - }, - "2": { - "then": "Ein privater Innenbereich wird überwacht, z. B. ein Geschäft, eine private Tiefgarage, ..." - } - }, - "question": "Um was für eine Überwachungskamera handelt es sich" - }, "Surveillance:zone": { "mappings": { "0": { @@ -2728,22 +2549,8 @@ "render": " Überwacht ein/e {surveillance:zone}" }, "camera:mount": { - "mappings": { - "0": { - "then": "Diese Kamera ist an einer Wand montiert" - }, - "1": { - "then": "Diese Kamera ist an einer Stange montiert" - }, - "2": { - "then": "Diese Kamera ist an der Decke montiert" - } - }, "question": "Wie ist diese Kamera montiert?", "render": "Montageart: {mount}" - }, - "direction. We don't ask this for a dome on a pole or ceiling as it has a 360° view": { - "question": "In welche Himmelsrichtung ist diese Kamera ausgerichtet?" } }, "title": { @@ -2829,25 +2636,6 @@ "question": "Wie viel muss man für diese Toiletten bezahlen?", "render": "Die Gebühr beträgt {charge}" }, - "toilet-handwashing": { - "mappings": { - "0": { - "then": "Diese Toilette verfügt über ein Waschbecken" - }, - "1": { - "then": "Diese Toilette verfügt über kein Waschbecken" - } - }, - "question": "Verfügt diese Toilette über ein Waschbecken?" - }, - "toilet-has-paper": { - "mappings": { - "1": { - "then": "Für diese Toilette müssen Sie Ihr eigenes Toilettenpapier mitbringen" - } - }, - "question": "Muss man für diese Toilette sein eigenes Toilettenpapier mitbringen?" - }, "toilets-changing-table": { "mappings": { "0": { @@ -2905,27 +2693,6 @@ }, "trail": { "name": "Wanderwege", - "tagRenderings": { - "Color": { - "mappings": { - "0": { - "then": "Blauer Weg" - }, - "1": { - "then": "Roter Weg" - }, - "2": { - "then": "Grüner Weg" - }, - "3": { - "then": "Gelber Weg" - } - } - }, - "trail-length": { - "render": "Der Wanderweg ist {_length:km} Kilometer lang" - } - }, "title": { "render": "Wanderweg" } @@ -2949,38 +2716,12 @@ "tagRenderings": { "tree-decidouous": { "mappings": { - "0": { - "then": "Laubabwerfend: Der Baum verliert für eine gewisse Zeit des Jahres seine Blätter." - }, "1": { "then": "immergrüner Baum." } }, "question": "Ist dies ein Nadelbaum oder ein Laubbaum?" }, - "tree-denotation": { - "mappings": { - "0": { - "then": "Der Baum ist aufgrund seiner Größe oder seiner markanten Lage bedeutsam. Er ist nützlich zur Orientierung." - }, - "1": { - "then": "Der Baum ist ein Naturdenkmal, z. B. weil er besonders alt ist oder zu einer wertvollen Art gehört." - }, - "2": { - "then": "Der Baum wird für landwirtschaftliche Zwecke genutzt, z. B. in einer Obstplantage." - }, - "3": { - "then": "Der Baum steht in einem Park oder ähnlichem (Friedhof, Schulgelände, ...)." - }, - "5": { - "then": "Dieser Baum steht entlang einer Straße." - }, - "7": { - "then": "Dieser Baum steht außerhalb eines städtischen Gebiets." - } - }, - "question": "Wie bedeutsam ist dieser Baum? Wählen Sie die erste Antwort, die zutrifft." - }, "tree-height": { "mappings": { "0": { @@ -2991,12 +2732,6 @@ }, "tree-heritage": { "mappings": { - "0": { - "then": "\"\"/ Als Denkmal registriert von der Onroerend Erfgoed Flandern" - }, - "1": { - "then": "Als Denkmal registriert von der Direction du Patrimoine culturel Brüssel" - }, "3": { "then": "Nicht als Denkmal registriert" } @@ -3014,8 +2749,7 @@ "2": { "then": "\"\"/ Dauerhaft blattlos" } - }, - "question": "Ist dies ein Laub- oder Nadelbaum?" + } }, "tree_node-name": { "mappings": { @@ -3025,12 +2759,6 @@ }, "question": "Hat der Baum einen Namen?", "render": "Name: {name}" - }, - "tree_node-ref:OnroerendErfgoed": { - "question": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?" - }, - "tree_node-wikidata": { - "question": "Was ist das passende Wikidata Element zu diesem Baum?" } }, "title": { @@ -3080,6 +2808,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Abfalleimer" + } + } + } + } + }, "name": "Abfalleimer", "presets": { "0": { @@ -3087,20 +2826,6 @@ } }, "tagRenderings": { - "dispensing_dog_bags": { - "mappings": { - "0": { - "then": "Dieser Abfalleimer verfügt über einen Spender für (Hunde-)Kotbeutel" - }, - "1": { - "then": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" - }, - "2": { - "then": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" - } - }, - "question": "Verfügt dieser Abfalleimer über einen Spender für (Hunde-)Kotbeutel?" - }, "waste-basket-waste-types": { "mappings": { "0": { @@ -3118,8 +2843,7 @@ "4": { "then": "Mülleimer für Drogen" } - }, - "question": "Um was für einen Abfalleimer handelt es sich?" + } } }, "title": { diff --git a/langs/layers/en.json b/langs/layers/en.json index bc4d34c4a..c44d83c6c 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -1936,6 +1936,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Defibrillators", "presets": { "0": { @@ -3375,6 +3382,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Waste Basket" + } + } + } + } + }, "name": "Waste Basket", "presets": { "0": { diff --git a/langs/layers/fr.json b/langs/layers/fr.json index b4c06d558..0179765fd 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -746,6 +746,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Défibrillateurs", "presets": { "0": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 229bb82e0..fa0df9532 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -746,8 +746,6 @@ } }, "charging_station": { - "description": "Una stazione di ricarica", - "name": "Stazioni di ricarica", "tagRenderings": { "Network": { "question": "A quale rete appartiene questa stazione di ricarica?", @@ -756,12 +754,16 @@ "OH": { "question": "Quali sono gli orari di apertura di questa stazione di ricarica?" } - }, - "title": { - "render": "Stazione di ricarica" } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Defibrillatori", "presets": { "0": { diff --git a/langs/layers/ja.json b/langs/layers/ja.json index b3385cb20..a3baa6845 100644 --- a/langs/layers/ja.json +++ b/langs/layers/ja.json @@ -73,8 +73,6 @@ } }, "charging_station": { - "description": "充電ステーション", - "name": "充電ステーション", "tagRenderings": { "Network": { "question": "この充電ステーションの運営チェーンはどこですか?", @@ -83,9 +81,6 @@ "OH": { "question": "この充電ステーションはいつオープンしますか?" } - }, - "title": { - "render": "充電ステーション" } }, "food": { diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json index 0ca42f3e8..80a29ad8d 100644 --- a/langs/layers/nb_NO.json +++ b/langs/layers/nb_NO.json @@ -171,8 +171,6 @@ } }, "charging_station": { - "description": "En ladestasjon", - "name": "Ladestasjoner", "tagRenderings": { "Network": { "render": "{network}" @@ -180,9 +178,6 @@ "OH": { "question": "Når åpnet denne ladestasjonen?" } - }, - "title": { - "render": "Ladestasjon" } }, "ghost_bike": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index bec8ca373..e766ae1f3 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -896,6 +896,13 @@ "icon": { "render": "./assets/layers/birdhide/birdhide.svg" }, + "mapRendering": { + "0": { + "icon": { + "render": "./assets/layers/birdhide/birdhide.svg" + } + } + }, "name": "Vogelkijkhutten", "presets": { "0": { @@ -3799,6 +3806,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Vuilnisbak" + } + } + } + } + }, "name": "Vuilnisbak", "presets": { "0": { @@ -3806,6 +3824,20 @@ } }, "tagRenderings": { + "dispensing_dog_bags": { + "mappings": { + "0": { + "then": "Deze vuilnisbak heeft een verdeler voor hondenpoepzakjes" + }, + "1": { + "then": "Deze vuilnisbak heeft geenverdeler voor hondenpoepzakjes" + }, + "2": { + "then": "Deze vuilnisbaak heeft waarschijnlijk geen verdeler voor hondenpoepzakjes" + } + }, + "question": "Heeft deze vuilnisbak een verdeler voor hondenpoepzakjes?" + }, "waste-basket-waste-types": { "mappings": { "0": { @@ -3828,20 +3860,6 @@ } }, "question": "Wat voor soort vuilnisbak is dit?" - }, - "dispensing_dog_bags": { - "mappings": { - "0": { - "then": "Deze vuilnisbak heeft een verdeler voor hondenpoepzakjes" - }, - "1": { - "then": "Deze vuilbak heeft geen verdeler voor hondenpoepzakjes" - }, - "2": { - "then": "Deze vuilnisbak heeft geen verdeler voor hondenpoepzakjes" - } - }, - "question": "Heeft deze vuilnisbak een verdeler voor hondenpoepzakjes?" } }, "title": { diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 33c93089d..d05c7ef76 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -632,8 +632,6 @@ } }, "charging_station": { - "description": "Зарядная станция", - "name": "Зарядные станции", "presets": { "0": { "title": "Зарядная станция" @@ -648,9 +646,6 @@ "question": "В какое время работает эта зарядная станция?" } }, - "title": { - "render": "Зарядная станция" - }, "units": { "0": { "applicableUnits": { @@ -712,6 +707,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Дефибрилляторы", "presets": { "0": { @@ -1436,6 +1438,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Контейнер для мусора" + } + } + } + } + }, "name": "Контейнер для мусора", "presets": { "0": { diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json index e5e5f65bf..951be71a1 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -446,8 +446,6 @@ } }, "charging_station": { - "description": "充電站", - "name": "充電站", "tagRenderings": { "Network": { "question": "充電站所屬的網路是?", @@ -456,9 +454,6 @@ "OH": { "question": "何時是充電站開放使用的時間?" } - }, - "title": { - "render": "充電站" } }, "ghost_bike": { diff --git a/langs/nan.json b/langs/nan.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/nan.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/nb_NO.json b/langs/nb_NO.json index bcc108765..7b3748873 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -1,29 +1,276 @@ { - "general": { - "skip": "Hopp over dette spørsmålet", - "cancel": "Avbryt", - "save": "Lagre", - "search": { - "searching": "Søker …" + "general": { + "skip": "Hopp over dette spørsmålet", + "cancel": "Avbryt", + "save": "Lagre", + "search": { + "searching": "Søker …", + "search": "Søk etter et sted", + "nothing": "Resultatløst …", + "error": "Noe gikk galt ." + }, + "welcomeBack": "Du er innlogget. Velkommen tilbake.", + "pdf": { + "versionInfo": "v{version}. Generert {date}", + "attr": "Kartdata © OpenStreetMap-bidragsytere, gjenbrukbart med ODbL-lisens", + "generatedWith": "Generert av MapComplete.osm.be", + "attrBackground": "Bakgrunnslag: {background}" + }, + "loginToStart": "Logg inn for å besvare dette spørsmålet", + "osmLinkTooltip": "Vis dette objektet på OpenStreetMap for historikk og flere redigeringsvalg", + "add": { + "presetInfo": "Det nye interessepunktet vil få {tags}", + "zoomInFurther": "Forstørr mer for å legge til et punkt.", + "title": "Legg til et nytt punkt?", + "intro": "Du klikket et sted der ingen data er kjent enda.
", + "addNewMapLabel": "Legg til nytt element", + "confirmIntro": "

Legg til {title} her?

Punktet du oppretter her vil være synlig for alle. Kun legg til ting på kartet hvis de virkelig finnes. Mange programmer bruker denne dataen.", + "layerNotEnabled": "Laget {layer} er ikke påslått. Skru på dette laget for å legge til et punkt.", + "confirmButton": "Legg til en {category} her.
Din endring er synlig for alle
", + "openLayerControl": "Åpne lagkontrollboksen", + "hasBeenImported": "Dette punktet har allerede blitt importert", + "stillLoading": "Dataen lastes fremdeles inn. Vent litt før du legger til et nytt punkt.", + "warnVisibleForEveryone": "Din endring vil være synlig for alle", + "zoomInMore": "Forstørr mer for å importere denne funksjonen", + "disableFilters": "Skru av alle filtre", + "disableFiltersExplanation": "Det kan hende noen funksjoner er skjult av et filter", + "addNew": "Legg til en ny {category} her", + "pleaseLogin": "Logg inn for å legge til et nytt punkt" + }, + "noNameCategory": "{category} uten navn", + "morescreen": { + "requestATheme": "Hvis du ønsker et brukerdefinert tema kan du forespørre det i feilsporeren", + "intro": "

Flere temakart?

Liker du å samle inn geodata?
Det er flere tilgjengelige temaer.", + "createYourOwnTheme": "Opprett ditt eget MapComplete-tema fra grunnen av", + "hiddenExplanation": "Disse temaene er kun tilgjengelige hvis du kjenner lenken. Du har oppdaget {hidden_discovered} av {total_hidden} hidden tema.", + "previouslyHiddenTitle": "Tidligere besøkte skjulte tema", + "streetcomplete": "Et annet lignende program er StreetComplete." + }, + "questions": { + "emailIs": "E-postadressen til {category} er {email}", + "websiteIs": "Nettside: {website}", + "emailOf": "Hva er e-postadressen til {category}?", + "phoneNumberOf": "Hva er telefonnummeret til {category}?", + "websiteOf": "Hva er nettsiden til {category}?", + "phoneNumberIs": "Telefonnummeret til denne {category} er {phone}" + }, + "sharescreen": { + "thanksForSharing": "Takk for at du bidrar.", + "copiedToClipboard": "Lenke kopiert til utklippstavle", + "intro": "

Del dette kartet

Del dette kartet ved å kopiere lenken nedenfor og sende den til venner og familie:", + "fsUserbadge": "Skru på innloggingsknappen", + "fsSearch": "Skru på søkefeltet", + "fsWelcomeMessage": "Vis velkomst-oppsprettsmeldinger og tilknyttede faner", + "editThisTheme": "Rediger dette temaet", + "fsGeolocation": "Skru på «Geolokaliser meg»-knappen (kun for mobil)", + "fsIncludeCurrentBackgroundMap": "Inkluder nåværende bakgrunnsvalg {name}", + "fsLayerControlToggle": "Start med lagkontrollen utvidet", + "addToHomeScreen": "

Legg til på hjemmeskjermen din

Du kan enkelt legge til denne nettsiden på din smarttelefon-hjemmeskjerm for å få det hele integrert. Klikk på «Legg til på hjemmeskjerm»-knappen i nettadressefeltet for å gjøre dette.", + "fsLayers": "Skru på lagkontrollen", + "fsIncludeCurrentLayers": "Inkluder nåværende lagvalg", + "fsIncludeCurrentLocation": "Inkluder nåværende posisjon", + "embedIntro": "

Bygg inn på nettsiden din

Legg til dette kartet på nettsiden din.
Du oppfordres til å gjøre dette, og trenger ikke å spørre om tillatelse.
Det er fritt, og vil alltid være det. Desto flere som bruker dette, desto mer verdifullt blir det." + }, + "attribution": { + "mapContributionsBy": "Den dataen som er synlig nå har redigeringer gjort av {contributors}", + "attributionContent": "

All data er fra OpenStreetMap, fritt gjenbrukbart med Open DataBase-lisens.

", + "codeContributionsBy": "MapComplete har blitt bygd av {contributors} og {hiddenCount} bidragsytere til", + "mapContributionsByAndHidden": "Data som vises nå har redigeringer gjort av {contributors} og {hiddenCount} andre bidragsytere", + "iconAttribution": { + "title": "Brukte ikoner" + }, + "themeBy": "Tema vedlikeholdt av {author}", + "attributionTitle": "Tilskrivelsesmerknad" + }, + "backgroundMap": "Bakgrunnskart", + "loginOnlyNeededToEdit": "hvis du ønsker å redigere kartet", + "readYourMessages": "Les alle OpenStreetMap-meldingene dine før du legger til et nytt punkt.", + "noTagsSelected": "Ingen etiketter valgt", + "customThemeIntro": "

Egendefinerte tema

Dette er tidligere besøkte brukergenererte tema.", + "layerSelection": { + "zoomInToSeeThisLayer": "Forstørr kartet hvis du vil se dette kartet", + "title": "Velg lag" + }, + "download": { + "downloadCSV": "Last ned synlig data som CSV", + "downloadAsPdfHelper": "Ideelt for utskrift av nåværende kart", + "noDataLoaded": "Ingen data innlastet enda. Nedlasting vil være tilgjengelig snart.", + "downloadAsPdf": "Last ned PDF av nåværende kart", + "downloadCSVHelper": "Kompatibelt med LibreOffice Calc, Excel, …", + "title": "Last ned synlig data", + "downloadGeojson": "Last ned synlig data som GeoJSON", + "licenseInfo": "

Opphavsrettsmerknad

Tilbudt data er tilgjengelig med ODbL-lisens. Gjenbruk er gratis for alle formål, men
  • tilskrivelsen© OpenStreetMap-bidragsytere kreves
  • Enhver endring må publiseres under samme lisens
Les hele opphavsrettsmerknaden for detaljer.", + "exporting": "Eksporterer …", + "includeMetaData": "Inkluder metadata (siste bidragsytere, utregnede verdier, …)", + "downloadGeoJsonHelper": "Kompatibelt med QGIS, ArcGIS, ESRI, …" + }, + "weekdays": { + "friday": "Fredag", + "saturday": "Lørdag", + "sunday": "Søndag", + "wednesday": "Onsdag", + "abbreviations": { + "thursday": "Tor", + "sunday": "Søn", + "monday": "Man", + "wednesday": "Ons", + "tuesday": "Tir", + "saturday": "Lør", + "friday": "Fre" + }, + "thursday": "Torsdag", + "monday": "Mandag", + "tuesday": "Tirsdag" + }, + "opening_hours": { + "openTill": "til", + "closed_until": "Stengt til {date}", + "open_24_7": "Døgnåpent", + "closed_permanently": "Stengt på ubestemt tid", + "ph_open_as_usual": "åpen som vanlig", + "loadingCountry": "Bestemmer land …", + "error_loading": "Feil: Kunne ikke visualisere disse åpningstidene.", + "open_during_ph": "På offentlige helligdager og ferier er dette stedet", + "not_all_rules_parsed": "Åpningstidene for dette stedet er kompliserte. Følgende regler ble sett bort fra i inndataelementet:", + "ph_not_known": " ", + "ph_closed": "stengt", + "opensAt": "fra", + "ph_open": "åpen" + }, + "histogram": { + "error_loading": "Kunne ikke laste inn histogrammet" + }, + "loading": "Laster inn …", + "openTheMap": "Åpne kartet", + "testing": "Testing. ingen endringer vil bli lagret.", + "wikipedia": { + "wikipediaboxTitle": "Wikipedia", + "loading": "Laster inn Wikipedia …", + "doSearch": "Søk ovenfor for å se resultater", + "noResults": "Fant ikke noe for {search}", + "noWikipediaPage": "Dette Wikipedia-elementet har ingen tilknyttet Wikipedia-side enda.", + "searchWikidata": "Søk på Wikipedia", + "createNewWikidata": "Opprett et nytt Wikipedia-element", + "failed": "Innlasting av Wikipedia-artikkel mislyktes" + }, + "returnToTheMap": "Gå tilbake til kartet", + "skippedQuestions": "Noen spørsmål ble hoppet over", + "oneSkippedQuestion": "Et spørsmål ble hoppet over", + "number": "tall", + "pickLanguage": "Velg språk: ", + "goToInbox": "Åpne innboks", + "getStartedNewAccount": " eller opprett en ny konto", + "getStartedLogin": "Logg inn med OpenStreetMap for å begynne", + "fewChangesBefore": "Besvar et par spørsmål for eksisterende punkter før du legger til et nytt." }, - "welcomeBack": "Du er innlogget. Velkommen tilbake." - }, - "index": { - "pickTheme": "Begynn ved å velge en av draktene nedenfor.", - "title": "Velkommen til MapComplete" - }, - "centerMessage": { - "ready": "Ferdig", - "zoomIn": "Forstørr for å vise eller redigere data", - "loadingData": "Laster inn data …" - }, - "image": { - "isDeleted": "Slettet", - "doDelete": "Fjern bilde", - "dontDelete": "Avbryt", - "uploadingMultiple": "Laster opp {count} bilder …", - "uploadingPicture": "Laster opp bildet ditt …", - "addPicture": "Legg til bilde", - "pleaseLogin": "Logg inn for å legge til et bilde" - } + "index": { + "pickTheme": "Begynn ved å velge et av temaene nedenfor.", + "title": "Velkommen til MapComplete", + "intro": "MapComplete er en OpenStreetMap-viser og redigerer, som viser deg info om funksjoner for et gitt tema og tillater oppdatering av det.", + "featuredThemeTitle": "Framhevet denne uken" + }, + "centerMessage": { + "ready": "Ferdig", + "zoomIn": "Forstørr for å vise eller redigere data", + "loadingData": "Laster inn data …", + "retrying": "Kunne ikke laste inn data. Prøver igjen om {count} sekunder …" + }, + "image": { + "isDeleted": "Slettet", + "doDelete": "Fjern bilde", + "dontDelete": "Avbryt", + "uploadingMultiple": "Laster opp {count} bilder …", + "uploadingPicture": "Laster opp bildet ditt …", + "addPicture": "Legg til bilde", + "pleaseLogin": "Logg inn for å legge til et bilde", + "ccbs": "med CC-BY-SA-lisens", + "ccb": "med CC-BY-lisens", + "willBePublished": "Ditt bilde vil bli publisert: ", + "uploadFailed": "Kunne ikke laste opp bildet ditt. Er du tilkoblet Internett og tillater tredjeparts-API-er? Brave-nettleseren eller uMatrix-programtillegget kan blokkere dem.", + "uploadDone": "Bildet ditt ble lagt til. Takk for at du hjelper til.", + "uploadMultipleDone": "{count} bilder har blitt lagt til. Takk for at du hjelper til.", + "toBig": "Bildet ditt på {actual_size} er for stort. Det kan maksimalt være {max_size}." + }, + "delete": { + "explanations": { + "hardDelete": "Dette punktet vil bli slettet i OpenStreetMap. Det kan gjenopprettes av en dreven bidragsyter.", + "softDelete": "Denne funksjonen vil bli oppdatert og skjult fra programmet. {reason}", + "selectReason": "Velg hvorfor denne funksjonen skal slettes" + }, + "delete": "Slett", + "isDeleted": "Denne funksjonen har blitt slettet", + "loginToDelete": "Du må være innlogget for å slette et punkt", + "cancel": "Avbryt", + "cannotBeDeleted": "Denne funksjonen kan ikke slettes", + "notEnoughExperience": "Dette punktet ble opprettet av noen andre.", + "loading": "Inspiserer egenskaper for å sjekke om denne funksjonen kan slettes.", + "whyDelete": "Hvorfor bør dette punktet slettes?", + "reasons": { + "duplicate": "Dette punktet er et duplikat av en annen funksjon", + "disused": "Denne funksjonen er ute av bruk eller fjernet", + "test": "Dette var et testpunkt, funksjonen var aldri operativ", + "notFound": "Fant ikke funksjonen" + }, + "safeDelete": "Dette punktet kan trygt slettes.", + "onlyEditedByLoggedInUser": "Dette punktet har kun blitt redigert av deg. Du kan trygt slette det.", + "useSomethingElse": "Bruk en annen OpenStreetMap-redigerer til å slette det istedenfor.", + "isntAPoint": "Kun punkter kan slettes, valgt funksjon er en vei, et område, eller en relasjon.", + "partOfOthers": "Dette punktet er en del av en vei eller relasjon, og kan derfor ikke slettes direkte.", + "readMessages": "Du har uleste meldinger. Les dette før sletting av et punkt, fordi noen kan ha tilbakemeldinger å komme med." + }, + "reviews": { + "posting_as": "Anmelder som", + "saving_review": "Lagrer …", + "title": "{count} vurderinger", + "no_rating": "Ingen vurdering gitt", + "plz_login": "Logg inn for å legge igjen en vurdering", + "write_a_comment": "Legg igjen en vurdering …", + "title_singular": "Én vurdering", + "name_required": "Et navn kreves for å vise og opprette vurderinger", + "affiliated_reviewer_warning": "(Tilknyttet vurdering)", + "saved": "Vurdering lagret. Takk for at du deler din mening.", + "no_reviews_yet": "Ingen vurderinger enda. Vær først til å skrive en og hjelp åpen data og bevegelsen.", + "tos": "Hvis du lager en vurdering, samtykker du til tjenestevilkårene til Mangrove.reviews", + "attribution": "Vurderinger er muliggjort av Mangrove Reviews og er tilgjengelige som CC-BY 4.0.", + "i_am_affiliated": "Jeg har en tilknytning til dette objektet
Sjekk om du er eier, skaper, ansatt, …" + }, + "move": { + "cancel": "Avbryt flytting", + "pointIsMoved": "Punktet har blitt flyttet", + "selectReason": "Hvorfor flytter du dette objektet?", + "loginToMove": "Du må være innlogget for å flytte et punkt", + "inviteToMoveAgain": "Flytt dette punktet igjen", + "moveTitle": "Flytt dette punktet", + "whyMove": "Hvorfor vil du flytte dette punktet?", + "confirmMove": "Flytt hit", + "reasons": { + "reasonInaccurate": "Posisjonen til dette objektet er unøyaktig og bør flyttes noen meter", + "reasonRelocation": "Objektet har blitt flyttet til et helt annet sted" + }, + "inviteToMove": { + "reasonInaccurate": "Forbedre nøyaktigheten for dette punktet", + "generic": "Flytt dette punktet", + "reasonRelocation": "Flytt dette objektet til et annet sted fordi det har blitt flyttet" + }, + "isRelation": "Denne funksjonen er en relasjon og kan ikke flyttes", + "cannotBeMoved": "Denne funksjonen kan ikke flyttes.", + "isWay": "Denne funksjonen er en vei. Bruk en annen OpenStreetMap-redigerer for å flytte den.", + "partOfRelation": "Denne funksjonen er en del av en relasjon. Bruk en annen redigerer for å flytte den.", + "partOfAWay": "Denne funksjonen er en del av en annen vei. Bruk en annen redigerer for å flytte den.", + "zoomInFurther": "Forstørr mer for å bekrefte denne flyttingen" + }, + "favourite": { + "reload": "Last inn dataen igjen" + }, + "split": { + "cancel": "Avbryt", + "loginToSplit": "Du må være innlogget for å dele en vei", + "split": "Del", + "splitTitle": "Velg hvor på kartet veien skal deles", + "hasBeenSplit": "Denne veien har blitt delt", + "inviteToSplit": "Inndel denne veien i mindre segmenter. Dette lar deg gi den forskjellige egenskaper for forskjellige strekk." + }, + "multi_apply": { + "autoApply": "Ved endring av attributteene {attr_names}, legges de automatisk til for {count} andre objekter også" + } } diff --git a/langs/nl.json b/langs/nl.json index 33a2cf04e..d0b5573e0 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -204,8 +204,8 @@ "includeMetaData": "Exporteer metadata (zoals laatste aanpassing, berekende waardes, …)", "downloadCSVHelper": "Compatibel met LibreOffice Calc, Excel, …", "downloadCSV": "Download de zichtbare data als CSV", - "downloadGeoJsonHelper": "Compatibel met QGIS, ArcGIS, ESRI, ...", - "downloadGeojson": "Download de zichtbare data als geojson", + "downloadGeoJsonHelper": "Compatibel met QGIS, ArcGIS, ESRI, …", + "downloadGeojson": "Download de zichtbare data als GeoJSON", "downloadAsPdfHelper": "Perfect om de huidige kaart af te printen", "downloadAsPdf": "Download een PDF van de huidig zichtbare kaart", "title": "Download de zichtbare data", @@ -215,7 +215,7 @@ "testing": "Testmode - wijzigingen worden niet opgeslaan", "openTheMap": "Naar de kaart", "wikipedia": { - "failed": "Het wikipedia-artikel inladen is mislukt", + "failed": "Het Wikipedia-artikel inladen is mislukt", "wikipediaboxTitle": "Wikipedia", "loading": "Wikipedia aan het laden...", "noWikipediaPage": "Dit Wikidata-item heeft nog geen overeenkomstig Wikipedia-artikel", diff --git a/langs/pt.json b/langs/pt.json index 21667811d..50537983e 100644 --- a/langs/pt.json +++ b/langs/pt.json @@ -19,13 +19,16 @@ "pleaseLogin": "Entre na sua conta para adicionar uma imagem", "uploadingMultiple": "A enviar {count} imagens…", "uploadingPicture": "A enviar a sua imagem…", - "addPicture": "Adicionar imagem" + "addPicture": "Adicionar imagem", + "uploadMultipleDone": "Foram adicionadas {count} fotografias. Obrigado por ajudar!", + "toBig": "A sua imagem é muito grande porque tem {actual_size}. Use imagens com o máximo {max_size}" }, "index": { "#": "Estes textos são mostrados acima dos botões do tema quando nenhum tema é carregado", "title": "Bem-vindo(a) ao MapComplete", "intro": "O MapComplete é um visualizador e editor do OpenStreetMap, que mostra informações sobre um tema específico.", - "pickTheme": "Escolha um tema abaixo para começar." + "pickTheme": "Escolha um tema abaixo para começar.", + "featuredThemeTitle": "Destaque desta semana" }, "delete": { "reasons": { diff --git a/langs/shared-questions/ca.json b/langs/shared-questions/ca.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/ca.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/eo.json b/langs/shared-questions/eo.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/eo.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/es.json b/langs/shared-questions/es.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/es.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/fi.json b/langs/shared-questions/fi.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/fi.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/hu.json b/langs/shared-questions/hu.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/hu.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/ja.json b/langs/shared-questions/ja.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/ja.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/nan.json b/langs/shared-questions/nan.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/nan.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/zh_HANŨS.json b/langs/shared-questions/zh_HANŨS.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/zh_HANŨS.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/shared-questions/zh_Hans.json b/langs/shared-questions/zh_Hans.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/shared-questions/zh_Hans.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/themes/de.json b/langs/themes/de.json index e01564288..237a48a78 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -36,7 +36,7 @@ "name": "Wohnmobilstellplätze", "presets": { "0": { - "description": "Fügen Sie einen neuen offiziellen Wohnmobilstellplatz hinzu. Dies sind ausgewiesene Plätze, an denen Sie in Ihrem Wohnmobil übernachten können. Sie können wie ein richtiger Campingplatz oder nur wie ein Parkplatz aussehen. Möglicherweise sind sie gar nicht ausgeschildert, sondern nur in einem Gemeindebeschluss festgelegt. Ein normaler Parkplatz für Wohnmobile, auf dem Übernachten nicht zulässig ist, ist kein Wohnmobilstellplatz. ", + "description": "Fügen Sie einen neuen offiziellen Wohnmobilstellplatz hinzu. Dies sind ausgewiesene Plätze, an denen Sie in Ihrem Wohnmobil übernachten können. Sie können wie ein richtiger Campingplatz oder nur wie ein Parkplatz aussehen. Möglicherweise sind sie gar nicht ausgeschildert, sondern nur in einem Gemeindebeschluss festgelegt. Ein normaler Parkplatz für Wohnmobile, auf dem übernachten nicht zulässig ist, ist kein Wohnmobilstellplatz. ", "title": "Wohnmobilstellplatz" } }, @@ -318,18 +318,6 @@ } }, "tagRenderings": { - "Bolts": { - "mappings": { - "0": { - "then": "Auf dieser Kletterroute sind keine Haken vorhanden" - }, - "1": { - "then": "Auf dieser Kletterroute sind keine Haken vorhanden" - } - }, - "question": "Wie viele Haken gibt es auf dieser Kletterroute bevor der Umlenker bzw. Standhaken erreicht ist?", - "render": "Diese Kletterroute hat {climbing:bolts} Haken" - }, "Difficulty": { "question": "Wie hoch ist der Schwierigkeitsgrad dieser Kletterroute nach dem französisch/belgischen System?", "render": "Die Schwierigkeit ist {climbing:grade:french} entsprechend des französisch/belgischen Systems" @@ -775,6 +763,17 @@ } } }, + "mapRendering": { + "0": { + "icon": { + "mappings": { + "0": { + "then": "./assets/themes/hackerspaces/led.png" + } + } + } + } + }, "name": "Hackerspace", "presets": { "0": { @@ -949,9 +948,6 @@ "station-name": { "question": "Wie lautet der Name dieser Feuerwache?" } - }, - "title": { - "render": "Feuerwache" } }, "3": { @@ -977,44 +973,6 @@ }, "openwindpowermap": { "description": "Eine Karte zum Anzeigen und Bearbeiten von Windkraftanlagen.", - "layers": { - "0": { - "name": "Windrad", - "presets": { - "0": { - "title": "Windrad" - } - }, - "title": { - "render": "Windrad" - }, - "units": { - "0": { - "applicableUnits": { - "0": { - "human": " Megawatt" - }, - "1": { - "human": " Kilowatt" - }, - "2": { - "human": " Watt" - }, - "3": { - "human": " Gigawatt" - } - } - }, - "1": { - "applicableUnits": { - "0": { - "human": " Meter" - } - } - } - } - } - }, "title": "OpenWindPowerMap" }, "parkings": { @@ -1032,35 +990,8 @@ }, "postboxes": { "layers": { - "0": { - "description": "Die Ebene zeigt Briefkästen.", - "name": "Brieflästen", - "presets": { - "0": { - "title": "Briefkasten" - } - }, - "title": { - "render": "Briefkasten" - } - }, "1": { "description": "Eine Ebene mit Postämtern.", - "filter": { - "0": { - "options": { - "0": { - "question": "Aktuell geöffnet" - } - } - } - }, - "name": "Poststellen", - "presets": { - "0": { - "title": "Poststelle" - } - }, "tagRenderings": { "OH": { "mappings": { @@ -1069,9 +1000,6 @@ } } } - }, - "title": { - "render": "Poststelle" } } }, diff --git a/langs/themes/en.json b/langs/themes/en.json index 7712adc64..7d19b6d5d 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -792,6 +792,9 @@ "description": "A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.

On this map, one can see all the ghost bikes which are known by OpenStreetMap. Is a ghost bike missing? Everyone can add or update information here - you only need to have a (free) OpenStreetMap account.", "title": "Ghost bikes" }, + "grb": { + "description": "This theme is an attempt to help automating the GRB import.
Note that this is very hacky and 'steals' the GRB data from an external site; in order to do this, you need to install and activate this firefox extension for it to work." + }, "hackerspaces": { "description": "On this map you can see hackerspaces, add a new hackerspace or update data directly", "layers": { @@ -804,6 +807,17 @@ } } }, + "mapRendering": { + "0": { + "icon": { + "mappings": { + "0": { + "then": "./assets/themes/hackerspaces/led.png" + } + } + } + } + }, "name": "Hackerspace", "presets": { "0": { @@ -1226,6 +1240,25 @@ "shortDescription": "An editable map with basic shop information", "title": "Open Shop Map" }, + "sidewalks": { + "description": "Experimental theme", + "layers": { + "0": { + "description": "Layer showing sidewalks of highways", + "name": "Sidewalks", + "tagRenderings": { + "streetname": { + "render": "This street is named {name}" + } + }, + "title": { + "render": "{name}" + } + } + }, + "shortDescription": "Sidewalk mapping", + "title": "Sidewalks" + }, "sport_pitches": { "description": "A sport pitch is an area where sports are played", "shortDescription": "A map showing sport pitches", diff --git a/langs/themes/nan.json b/langs/themes/nan.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/themes/nan.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 5dcbdcda9..fce0d71ef 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -197,24 +197,6 @@ }, "caravansites-website": { "render": "Officiële website: : {website}" - }, - "caravansites-internet": { - "mappings": { - "0": { - "then": "Er is internettoegang" - }, - "1": { - "then": "Er is internettoegang" - } - } - }, - "caravansites-description": { - "question": "Wil je graag een algemene beschrijving toevoegen van deze plaats? (Herhaal hier niet de antwoorden op de vragen die reeds gesteld zijn. Hou het objectief - je kan je mening geven via een review)", - "render": "Meer details over deze plaats: {description}" - }, - "caravansites-capacity": { - "question": "Hoeveel campers kunnen hier overnachten? (sla dit over als er geen duidelijk aantal plaatsen of aangeduid maximum is)", - "render": "{capacity} campers kunnen deze plaats tegelijk gebruiken" } }, "title": { @@ -753,7 +735,50 @@ "grb": { "description": "GRB Fixup", "layers": { - "0": { + "3": { + "description": "Dit gebouw heeft een foutmelding", + "name": "Fixmes op gebouwen", + "tagRenderings": { + "grb-fixme": { + "mappings": { + "0": { + "then": "Geen fixme" + } + }, + "question": "Wat zegt de fixme?", + "render": "De fixme is {fixme}" + }, + "grb-housenumber": { + "mappings": { + "0": { + "then": "Geen huisnummer" + } + }, + "question": "Wat is het huisnummer?", + "render": "Het huisnummer is {addr:housenumber}" + }, + "grb-min-level": { + "question": "Hoeveel verdiepingen ontbreken?", + "render": "Dit gebouw begint maar op de {building:min_level} verdieping" + }, + "grb-street": { + "question": "Wat is de straat?", + "render": "De straat is {addr:street}" + }, + "grb-unit": { + "render": "De wooneenheid-aanduiding is {addr:unit} " + } + }, + "title": { + "mappings": { + "0": { + "then": "{fixme}" + } + }, + "render": "{addr:street} {addr:housenumber}" + } + }, + "5": { "description": "Dit gebouw heeft een foutmelding", "name": "Fixmes op gebouwen", "tagRenderings": { diff --git a/langs/themes/pt.json b/langs/themes/pt.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/themes/pt.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/themes/zh_HANŨS.json b/langs/themes/zh_HANŨS.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/themes/zh_HANŨS.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/langs/themes/zh_Hans.json b/langs/themes/zh_Hans.json deleted file mode 100644 index 0967ef424..000000000 --- a/langs/themes/zh_Hans.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/package-lock.json b/package-lock.json index ca84f2e65..fc0315a4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", + "leaflet-polylineoffset": "^1.1.1", "leaflet-providers": "^1.13.0", "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", @@ -10034,6 +10035,11 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, + "node_modules/leaflet-polylineoffset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/leaflet-polylineoffset/-/leaflet-polylineoffset-1.1.1.tgz", + "integrity": "sha512-WcEjAROx9IhIVwSMoFy9p2QBCG9YeuGtJl4ZdunIgj4xbCdTrUkBj8JdonUeCyLPnD2/Vrem/raOPHm5LvebSw==" + }, "node_modules/leaflet-providers": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", @@ -25964,6 +25970,11 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, + "leaflet-polylineoffset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/leaflet-polylineoffset/-/leaflet-polylineoffset-1.1.1.tgz", + "integrity": "sha512-WcEjAROx9IhIVwSMoFy9p2QBCG9YeuGtJl4ZdunIgj4xbCdTrUkBj8JdonUeCyLPnD2/Vrem/raOPHm5LvebSw==" + }, "leaflet-providers": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", diff --git a/package.json b/package.json index ea7d04f0f..21a1a7e8e 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", + "leaflet-polylineoffset": "^1.1.1", "leaflet-providers": "^1.13.0", "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 65741b72f..9d7951d0f 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -1,4 +1,5 @@ import {Utils} from "../Utils"; + Utils.runningFromConsole = true; import SpecialVisualizations from "../UI/SpecialVisualizations"; import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; @@ -11,7 +12,8 @@ import {writeFileSync} from "fs"; import State from "../State"; import {QueryParameters} from "../Logic/Web/QueryParameters"; import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; - +import Minimap from "../UI/Base/Minimap"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; function WriteFile(filename, html: string | BaseUIElement, autogenSource: string[]): void { @@ -20,13 +22,18 @@ function WriteFile(filename, html: string | BaseUIElement, autogenSource: string ]).AsMarkdown()); } -WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage, ["UI/SpecialVisualisations.ts"]) +WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), ["UI/SpecialVisualisations.ts"]) WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()]).SetClass("flex-col"), ["SimpleMetaTagger", "ExtraFunction"]) WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), ["ValidatedTextField.ts"]); +Minimap.createMiniMap = _ => { + console.log("Not creating a minimap, it is disabled"); + return undefined +} -new State(new LayoutConfig({ + +const dummyLayout = new LayoutConfig({ language: ["en"], id: "", maintainer: "pietervdvn", @@ -43,11 +50,15 @@ new State(new LayoutConfig({ id: "", source: { osmTags: "id~*" - } + }, + mapRendering: [] } ] -})) +}) + +new FeatureSwitchState(dummyLayout) + QueryParameters.GetQueryParameter("layer-", "true", "Wether or not the layer with id is shown") WriteFile("./Docs/URL_Parameters.md", QueryParameters.GenerateQueryParameterDocs(), ["QueryParameters"]) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index e4c981dec..da1a984b4 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -12,8 +12,8 @@ import {Utils} from "../Utils"; // It spits out an overview of those to be used to load them interface LayersAndThemes { - themes: any[], - layers: { parsed: any, path: string }[] + themes: LayoutConfigJson[], + layers: { parsed: LayerConfigJson, path: string }[] } @@ -35,7 +35,6 @@ class LayerOverviewUtils { } } - writeFiles(lt: LayersAndThemes) { writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ "layers": lt.layers.map(l => l.parsed), @@ -43,7 +42,6 @@ class LayerOverviewUtils { })) } - validateLayer(layerJson: LayerConfigJson, path: string, knownPaths: Set, context?: string): string[] { let errorCount = []; if (layerJson["overpassTags"] !== undefined) { @@ -109,6 +107,8 @@ class LayerOverviewUtils { } let themeErrorCount = [] + // used only for the reports + let themeConfigs: LayoutConfig[] = [] for (const themeInfo of themeFiles) { const themeFile = themeInfo.parsed const themePath = themeInfo.path @@ -119,7 +119,7 @@ class LayerOverviewUtils { themeErrorCount.push("The theme " + themeFile.id + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ") } if (themeFile["roamingRenderings"] !== undefined) { - themeErrorCount.push("Theme " + themeFile.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead") + themeErrorCount.push("Theme " + themeFile.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead") } for (const layer of themeFile.layers) { if (typeof layer === "string") { @@ -144,17 +144,17 @@ class LayerOverviewUtils { } } } - + const referencedLayers = Utils.NoNull([].concat(...themeFile.layers.map(layer => { - if(typeof layer === "string"){ + if (typeof layer === "string") { return layer } - if(layer["builtin"] !== undefined){ + if (layer["builtin"] !== undefined) { return layer["builtin"] } return undefined }).map(layerName => { - if(typeof layerName === "string"){ + if (typeof layerName === "string") { return [layerName] } return layerName @@ -176,9 +176,9 @@ class LayerOverviewUtils { } const neededLanguages = themeFile["mustHaveLanguage"] if (neededLanguages !== undefined) { - console.log("Checking language requerements for ", theme.id, "as it must have", neededLanguages.join(", ")) - const allTranslations = [].concat(Translation.ExtractAllTranslationsFrom(theme, theme.id), - ...referencedLayers.map(layerId => Translation.ExtractAllTranslationsFrom(knownLayerIds.get(layerId), theme.id+"->"+layerId))) + console.log("Checking language requirements for ", theme.id, "as it must have", neededLanguages.join(", ")) + const allTranslations = [].concat(Translation.ExtractAllTranslationsFrom(theme, theme.id), + ...referencedLayers.map(layerId => Translation.ExtractAllTranslationsFrom(knownLayerIds.get(layerId), theme.id + "->" + layerId))) for (const neededLanguage of neededLanguages) { allTranslations .filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined) @@ -189,7 +189,7 @@ class LayerOverviewUtils { } - + themeConfigs.push(theme) } catch (e) { themeErrorCount.push("Could not parse theme " + themeFile["id"] + "due to", e) } @@ -210,12 +210,11 @@ class LayerOverviewUtils { console.log(msg) console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - if (process.argv.indexOf("--report") >= 0) { + if (args.indexOf("--report") >= 0) { console.log("Writing report!") writeFileSync("layer_report.txt", errors) } - - if (process.argv.indexOf("--no-fail") < 0) { + if (args.indexOf("--no-fail") < 0) { throw msg; } } diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 788060549..760fbbd4e 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -31,6 +31,9 @@ class TranslationPart { if (!translations.hasOwnProperty(translationsKey)) { continue; } + if(translationsKey == "then"){ + throw "Suspicious translation at "+context + } const v = translations[translationsKey] if (typeof (v) != "string") { console.error("Non-string object in translation while trying to add more translations to '", translationsKey, "': ", v) diff --git a/scripts/lint.ts b/scripts/lint.ts index fe79b6130..e883fa3ae 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -1,56 +1,23 @@ +import ScriptUtils from "./ScriptUtils"; +import {writeFileSync} from "fs"; +import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; +import LineRenderingConfigJson from "../Models/ThemeConfig/Json/LineRenderingConfigJson"; +import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; + /* * This script reads all theme and layer files and reformats them inplace * Use with caution, make a commit beforehand! */ - -import ScriptUtils from "./ScriptUtils"; -import {readFileSync, writeFileSync} from "fs"; -import {tag} from "@turf/turf"; -import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; - -/** - * In place fix - */ -function fixLayerConfig(config: LayerConfigJson) : void{ - if(config.tagRenderings === undefined){ - return - } - - for (const tagRendering of config.tagRenderings) { - if(tagRendering["#"] !== undefined){ - tagRendering["id"] = tagRendering["#"] - delete tagRendering["#"] - } - if(tagRendering["id"] === undefined){ - if(tagRendering["freeform"]?.key !== undefined ) { - tagRendering["id"] = config.id+"-"+tagRendering["freeform"]["key"] - } - } - } -} - const layerFiles = ScriptUtils.getLayerFiles(); for (const layerFile of layerFiles) { - fixLayerConfig(layerFile.parsed) + LegacyJsonConvert. fixLayerConfig(layerFile.parsed) writeFileSync(layerFile.path, JSON.stringify(layerFile.parsed, null, " ")) } const themeFiles = ScriptUtils.getThemeFiles() for (const themeFile of themeFiles) { - for (const layerConfig of themeFile.parsed.layers ?? []) { - if(typeof layerConfig === "string" || layerConfig["builtin"]!== undefined){ - continue - } - // @ts-ignore - fixLayerConfig(layerConfig) - } - - if(themeFile.parsed["roamingRenderings"] !== undefined && themeFile.parsed["roamingRenderings"].length == 0){ - delete themeFile.parsed["roamingRenderings"] - } - + LegacyJsonConvert.fixThemeConfig(themeFile.parsed) writeFileSync(themeFile.path, JSON.stringify(themeFile.parsed, null, " ")) } -//*/ diff --git a/scripts/slice.ts b/scripts/slice.ts new file mode 100644 index 000000000..342dc22f9 --- /dev/null +++ b/scripts/slice.ts @@ -0,0 +1,147 @@ +import * as fs from "fs"; +import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource"; +import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; +import * as readline from "readline"; +import ScriptUtils from "./ScriptUtils"; + +async function readFeaturesFromLineDelimitedJsonFile(inputFile: string): Promise { + const fileStream = fs.createReadStream(inputFile); + + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + // Note: we use the crlfDelay option to recognize all instances of CR LF + // ('\r\n') in input.txt as a single line break. + + const allFeatures: any[] = [] + // @ts-ignore + for await (const line of rl) { + try { + allFeatures.push(JSON.parse(line)) + } catch (e) { + console.error("Could not parse", line) + break + } + if (allFeatures.length % 10000 === 0) { + ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now") + } + } + return allFeatures +} + +async function readGeojsonLineByLine(inputFile: string): Promise { + const fileStream = fs.createReadStream(inputFile); + + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + // Note: we use the crlfDelay option to recognize all instances of CR LF + // ('\r\n') in input.txt as a single line break. + + const allFeatures: any[] = [] + let featuresSeen = false + // @ts-ignore + for await (let line: string of rl) { + if (!featuresSeen && line.startsWith("\"features\":")) { + featuresSeen = true; + continue; + } + if (!featuresSeen) { + continue + } + if (line.endsWith(",")) { + line = line.substring(0, line.length - 1) + } + + try { + allFeatures.push(JSON.parse(line)) + } catch (e) { + console.error("Could not parse", line) + break + } + if (allFeatures.length % 10000 === 0) { + ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now") + } + } + return allFeatures +} + +async function readFeaturesFromGeoJson(inputFile: string): Promise { + try { + return JSON.parse(fs.readFileSync(inputFile, "UTF-8")).features + } catch (e) { + // We retry, but with a line-by-line approach + return await readGeojsonLineByLine(inputFile) + } +} + +async function main(args: string[]) { + + console.log("GeoJSON slicer") + if (args.length < 3) { + console.log("USAGE: ") + return + } + + const inputFile = args[0] + const zoomlevel = Number(args[1]) + const outputDirectory = args[2] + + if (!fs.existsSync(outputDirectory)) { + fs.mkdirSync(outputDirectory) + console.log("Directory created") + } + console.log("Using directory ", outputDirectory) + + + let allFeatures: any []; + if (inputFile.endsWith(".geojson")) { + allFeatures = await readFeaturesFromGeoJson(inputFile) + } else { + allFeatures = await readFeaturesFromLineDelimitedJsonFile(inputFile) + } + + + console.log("Loaded all", allFeatures.length, "points") + + const keysToRemove = ["STRAATNMID", "GEMEENTE", "POSTCODE"] + for (const f of allFeatures) { + for (const keyToRm of keysToRemove) { + delete f.properties[keyToRm] + } + delete f.bbox + } + + //const knownKeys = Utils.Dedup([].concat(...allFeatures.map(f => Object.keys(f.properties)))) + //console.log("Kept keys: ", knownKeys) + + TiledFeatureSource.createHierarchy( + new StaticFeatureSource(allFeatures, false), + { + minZoomLevel: zoomlevel, + maxZoomLevel: zoomlevel, + maxFeatureCount: Number.MAX_VALUE, + registerTile: tile => { + const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson` + const features = tile.features.data.map(ff => ff.feature) + features.forEach(f => { + delete f.bbox + }) + fs.writeFileSync(path, JSON.stringify({ + "type": "FeatureCollection", + "features": features + }, null, " ")) + ScriptUtils.erasableLog("Written ", path, "which has ", tile.features.data.length, "features") + } + } + ) + +} + +let args = [...process.argv] +args.splice(0, 2) +main(args).then(_ => { + console.log("All done!") +}); diff --git a/test.ts b/test.ts index a8cc94f92..7bc8c81d8 100644 --- a/test.ts +++ b/test.ts @@ -1,13 +1,26 @@ -import * as wd from "wikidata-sdk" -import * as wds from "wikibase-sdk" -import {Utils} from "./Utils"; - -const url = wd.getEntities(["Q42"]) -console.log(url) -Utils.downloadJson(url).then(async (entities) => { - //const parsed = wd.parse.wb.entities(entities)["Q42"] - console.log(entities) - console.log(wds.simplify.entity(entities.entities["Q42"], { - timeConverter: 'simple-day' - })) -}) \ No newline at end of file +import ShowDataLayer from "./UI/ShowDataLayer/ShowDataLayer"; +import AllKnownLayers from "./Customizations/AllKnownLayers"; +import Minimap from "./UI/Base/Minimap"; +import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource"; +import MinimapImplementation from "./UI/Base/MinimapImplementation"; +import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; +import BaseLayer from "./Models/BaseLayer"; +import {UIEventSource} from "./Logic/UIEventSource"; +import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; +MinimapImplementation.initialize() +AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) +const confirmationMap = Minimap.createMiniMap({ + background: new UIEventSource(AvailableBaseLayers.osmCarto) +}) +const features = [{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823483},"geometry":{"type":"LineString","coordinates":[[3.216693,51.2147409],[3.2166930000000225,51.214740500000055]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823481},"geometry":{"type":"LineString","coordinates":[[3.2167247,51.2146969],[3.21671060000004,51.2147159000002]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823481},"geometry":{"type":"LineString","coordinates":[[3.2167247,51.2146969],[3.2167241999999976,51.214696799999714]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823549},"geometry":{"type":"LineString","coordinates":[[3.2168871,51.2147399],[3.2168876999999547,51.21474009999989]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289383},"geometry":{"type":"LineString","coordinates":[[3.2169973,51.2147676],[3.2169969000000034,51.21476780000005]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169673999999895,51.21481170000002]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.216949899999979,51.214808000000225]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169306,51.21480400000028]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169465999999756,51.214779199999825]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978288381},"geometry":{"type":"LineString","coordinates":[[3.2168856,51.2147638],[3.216885599999961,51.214763799999986]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289386},"geometry":{"type":"LineString","coordinates":[[3.2168815,51.2147718],[3.216881100000038,51.21477160000009]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289384},"geometry":{"type":"LineString","coordinates":[[3.2168674,51.2147683],[3.216867399999983,51.214768400000224]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823514},"geometry":{"type":"LineString","coordinates":[[3.2168551,51.2147863],[3.2168551000000436,51.21478629999984]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823483},"geometry":{"type":"LineString","coordinates":[[3.216693,51.2147409],[3.2166930000000225,51.214740500000055]]}},"freshness":"2021-11-02T20:06:53.088Z"}] +const changePreview = new StaticFeatureSource(features.map(f => f.feature), false) +console.log("ChangePreview", changePreview.features.data) +new ShowDataLayer({ + leafletMap: confirmationMap.leafletMap, + enablePopups: false, + zoomToFeatures: true, + features: changePreview, + layerToShow: AllKnownLayers.sharedLayers.get("conflation") +}) + +confirmationMap.SetStyle("height: 20rem").SetClass("w-full").AttachTo("maindiv") \ No newline at end of file diff --git a/test/ReplaceGeometry.spec.ts b/test/ReplaceGeometry.spec.ts new file mode 100644 index 000000000..40c3e53bb --- /dev/null +++ b/test/ReplaceGeometry.spec.ts @@ -0,0 +1,185 @@ +import T from "./TestHelper"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import {Utils} from "../Utils"; + +export default class ReplaceGeometrySpec extends T { + constructor() { + super("ReplaceGeometry", [ + ["Simple house replacement", async () => { + const coordinates = <[number, number][]>[[ + 3.216690793633461, + 51.21474084112525 + ], + [ + 3.2167256623506546, + 51.214696737309964 + ], + [ + 3.2169999182224274, + 51.214768983537674 + ], + [ + 3.2169650495052338, + 51.21480720678671 + ], + [ + 3.2169368863105774, + 51.21480090625335 + ], + [ + 3.2169489562511444, + 51.21478074454077 + ], + [ + 3.216886594891548, + 51.214765203214625 + ], + [ + 3.2168812304735184, + 51.21477192378873 + ], + [ + 3.2168644666671753, + 51.214768983537674 + ], + [ + 3.2168537378311157, + 51.21478746511261 + ], + [ + 3.216690793633461, + 51.21474084112525 + ] + ] + + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/160909312/full", + { + "version": "0.6", + "generator": "CGImap 0.8.5 (920083 spike-06.openstreetmap.org)", + "copyright": "OpenStreetMap and contributors", + "attribution": "http://www.openstreetmap.org/copyright", + "license": "http://opendatacommons.org/licenses/odbl/1-0/", + "elements": [{ + "type": "node", + "id": 1728823481, + "lat": 51.2146969, + "lon": 3.2167247, + "timestamp": "2017-07-18T22:52:45Z", + "version": 2, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 1728823483, + "lat": 51.2147409, + "lon": 3.216693, + "timestamp": "2017-07-18T22:52:45Z", + "version": 2, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 1728823514, + "lat": 51.2147863, + "lon": 3.2168551, + "timestamp": "2017-07-18T22:52:45Z", + "version": 2, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 1728823549, + "lat": 51.2147399, + "lon": 3.2168871, + "timestamp": "2017-07-18T22:52:46Z", + "version": 2, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 4978288381, + "lat": 51.2147638, + "lon": 3.2168856, + "timestamp": "2017-07-18T22:52:21Z", + "version": 1, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 4978289383, + "lat": 51.2147676, + "lon": 3.2169973, + "timestamp": "2017-07-18T22:52:21Z", + "version": 1, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 4978289384, + "lat": 51.2147683, + "lon": 3.2168674, + "timestamp": "2017-07-18T22:52:21Z", + "version": 1, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 4978289386, + "lat": 51.2147718, + "lon": 3.2168815, + "timestamp": "2017-07-18T22:52:21Z", + "version": 1, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "node", + "id": 4978289388, + "lat": 51.2147884, + "lon": 3.2169829, + "timestamp": "2017-07-18T22:52:21Z", + "version": 1, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209 + }, { + "type": "way", + "id": 160909312, + "timestamp": "2017-07-18T22:52:30Z", + "version": 2, + "changeset": 50391526, + "user": "catweazle67", + "uid": 1976209, + "nodes": [1728823483, 1728823514, 4978289384, 4978289386, 4978288381, 4978289388, 4978289383, 1728823549, 1728823481, 1728823483], + "tags": { + "addr:city": "Brugge", + "addr:country": "BE", + "addr:housenumber": "108", + "addr:postcode": "8000", + "addr:street": "Ezelstraat", + "building": "yes" + } + }] + } + ) + + + const wayId = "way/160909312" + const url = `https://www.openstreetmap.org/api/0.6/${wayId}/full`; + const rawData = await Utils.downloadJsonCached(url, 1000) + + + + + }] + ]); + } +} \ No newline at end of file diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 833a7afc5..790d641b5 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -166,7 +166,7 @@ export default class TagSpec extends T { } ], condition: "x=" - }, undefined, "Tests"); + }, "Tests"); equal(undefined, tr.GetRenderValue({"foo": "bar"})); equal("Has no name", tr.GetRenderValue({"noname": "yes"})?.txt); @@ -483,7 +483,7 @@ export default class TagSpec extends T { ] }; - const tagRendering = new TagRenderingConfig(config, null, "test"); + const tagRendering = new TagRenderingConfig(config, "test"); equal(true, tagRendering.IsKnown({bottle: "yes"})) equal(false, tagRendering.IsKnown({})) }], diff --git a/test/TestAll.ts b/test/TestAll.ts index 9b23957d1..c6f64d62e 100644 --- a/test/TestAll.ts +++ b/test/TestAll.ts @@ -13,6 +13,7 @@ import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec"; import WikidataSpecTest from "./Wikidata.spec.test"; import ImageProviderSpec from "./ImageProvider.spec"; import ActorsSpec from "./Actors.spec"; +import ReplaceGeometrySpec from "./ReplaceGeometry.spec"; ScriptUtils.fixUtils() @@ -29,7 +30,8 @@ const allTests = [ new TileFreshnessCalculatorSpec(), new WikidataSpecTest(), new ImageProviderSpec(), - new ActorsSpec() + new ActorsSpec(), + new ReplaceGeometrySpec() ] Utils.externalDownloadFunction = async (url) => {