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/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..db4308eb6 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop, master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '21 18 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy_pietervdvn.yml b/.github/workflows/deploy_pietervdvn.yml index dfa564468..9a2cf8c7c 100644 --- a/.github/workflows/deploy_pietervdvn.yml +++ b/.github/workflows/deploy_pietervdvn.yml @@ -37,6 +37,13 @@ jobs: git clone --depth 1 --single-branch --branch master "https://x-access-token:$DEPLOY_KEY_PIETERVDVN@github.com/pietervdvn/pietervdvn.github.io.git" echo "Destination repo is cloned" + - name: Sync repo + env: + DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }} + run: | + cd pietervdvn.github.io + git pull + - name: "Copying files" run: | echo "Deploying" 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..07f334db6 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 ---------------------------------- @@ -157,11 +166,12 @@ The above code will be executed for every feature in the layer. The feature is a Some advanced functions are available on **feat** as well: - - distanceTo - - overlapWith - - closest - - closestn - - memberships + - [distanceTo](#distanceTo) + - [overlapWith](#overlapWith) + - [closest](#closest) + - [closestn](#closestn) + - [memberships](#memberships) + - [get](#get) ### distanceTo @@ -172,7 +182,10 @@ 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. + Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.If the current feature is a point, all features that this point is embeded in are given. + +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')` @@ -202,4 +215,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/ContributorRights.md b/Docs/ContributorRights.md new file mode 100644 index 000000000..f845f0672 --- /dev/null +++ b/Docs/ContributorRights.md @@ -0,0 +1,32 @@ + + Rights of contributors + ====================== + +If a contributor is quite active within MapComplete, this contributor might be granted access to the main repository. + +If you have access to the repository, you can make a fork of an already existing branch and push this new branch to github. +This means that this branch will be _automatically built_ and be **deployed** to `https://pietervdvn.github.io/mc/`. You can see the deploy process on [Github Actions](https://github.com/pietervdvn/MapComplete/actions). +Don't worry about pushing too much. These deploys are free and totally automatic. They might fail if something is wrong, but this will hinder no-one. + +Additionaly, some other maintainer might step in and merge the latest develop with your branch, making later pull requests easier. + +Don't worry about bugs +---------------------- + +As a non-admin contributor, you can _not_ make changes to the `master` nor to the `develop` branch. This is because, as soon as master is changed, this is built and deployed on `mapcomplete.osm.be`, which a lot of people use. An error there will cause a lot of grieve. + +A push on `develop` is automatically deployed to [pietervdvn.github.io/mc/develop] and is used by quite some people to. People using this version should know that this is a testing ground for new features and might contain a bug every now and then. + +In other words, to get your theme deployed on the main instances, you'll still have to create a pull request. The maintainers will then doublecheck and pull it in. + +If you have a local repository +------------------------------ + +If you have made a fork earlier and have received contributor rights, you need to tell your local git repository that pushing to the main repository is possible. + +To do this: + +1. type `git remote add upstream git@github.com:pietervdvn/MapComplete` +2. Run `git push upstream` to push your latest changes to the main repo (and not your fork). Running `git push` will push to your fork. + +Alternatively, if you don't have any unmerged changes, you can remove your local copy and clone `pietervdvn/MapComplete` again to start fresh. \ 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..35d03abc0 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,97 @@ 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 argument `tags` 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) +targetLayer | _undefined_ | 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 +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 +Snap onto layer(s)/replace geometry with this other way | _undefined_ | - 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 + - If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list +snap max distance | 5 | The maximum distance that this point will move to snap onto a layer (in meters) #### Example usage - `{import_button(,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18)}` + `{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18,,5)}` + + ### multi_apply A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags 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_aed.json b/Docs/TagInfo/mapcomplete_aed.json index 115eb3525..b88de395d 100644 --- a/Docs/TagInfo/mapcomplete_aed.json +++ b/Docs/TagInfo/mapcomplete_aed.json @@ -112,7 +112,7 @@ }, { "key": "wheelchair", - "description": "Layer 'Defibrillators' 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 'Open AED Map')", + "description": "Layer 'Defibrillators' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open AED Map')", "value": "designated" }, { diff --git a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json index 630d8e52b..0e002e665 100644 --- a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json +++ b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json @@ -103,7 +103,7 @@ }, { "key": "wheelchair", - "description": "Layer 'Cafés and pubs' 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 'Cafés and pubs')", + "description": "Layer 'Cafés and pubs' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", "value": "designated" }, { diff --git a/Docs/TagInfo/mapcomplete_charging_stations.json b/Docs/TagInfo/mapcomplete_charging_stations.json index 6cd7b7772..41754afc4 100644 --- a/Docs/TagInfo/mapcomplete_charging_stations.json +++ b/Docs/TagInfo/mapcomplete_charging_stations.json @@ -48,7 +48,7 @@ }, { "key": "bicycle", - "description": "Layer 'Charging stations' shows bicycle=yes with a fixed text, namely 'bicycles can be charged here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "description": "Layer 'Charging stations' shows bicycle=yes with a fixed text, namely 'Bcycles can be charged here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", "value": "yes" }, { @@ -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,677 +252,66 @@ "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": "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": "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": "authentication:membership_card", - "description": "Layer 'Charging stations' shows authentication:membership_card=yes with a fixed text, namely 'Authentication by a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:app", - "description": "Layer 'Charging stations' shows authentication:app=yes with a fixed text, namely 'Authentication by an app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:phone_call", - "description": "Layer 'Charging stations' shows authentication:phone_call=yes with a fixed text, namely 'Authentication via phone call is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:short_message", - "description": "Layer 'Charging stations' shows authentication:short_message=yes with a fixed text, namely 'Authentication via phone call is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:nfc", - "description": "Layer 'Charging stations' shows authentication:nfc=yes with a fixed text, namely 'Authentication via NFC is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:money_card", - "description": "Layer 'Charging stations' shows authentication:money_card=yes with a fixed text, namely 'Authentication via Money Card is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "key": "authentication:debit_card", - "description": "Layer 'Charging stations' shows authentication:debit_card=yes with a fixed text, namely 'Authentication via debit card is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", - "value": "yes" - }, - { - "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')", - "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')" @@ -932,20 +321,75 @@ "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')", + "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": "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.", + "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')", @@ -966,6 +410,50 @@ "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", + "description": "Layer 'Charging stations' shows authentication:membership_card=yes with a fixed text, namely 'Authentication by a membership card' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:app", + "description": "Layer 'Charging stations' shows authentication:app=yes with a fixed text, namely 'Authentication by an app' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:phone_call", + "description": "Layer 'Charging stations' shows authentication:phone_call=yes with a fixed text, namely 'Authentication via phone call is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:short_message", + "description": "Layer 'Charging stations' shows authentication:short_message=yes with a fixed text, namely 'Authentication via SMS is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:nfc", + "description": "Layer 'Charging stations' shows authentication:nfc=yes with a fixed text, namely 'Authentication via NFC is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:money_card", + "description": "Layer 'Charging stations' shows authentication:money_card=yes with a fixed text, namely 'Authentication via Money Card is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:debit_card", + "description": "Layer 'Charging stations' shows authentication:debit_card=yes with a fixed text, namely 'Authentication via debit card is available' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "yes" + }, + { + "key": "authentication:none", + "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": "maxstay", "description": "Layer 'Charging stations' shows and asks freeform values for key 'maxstay' (in the MapComplete.osm.be theme 'Charging stations')" @@ -1053,49 +541,129 @@ "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=&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=&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=&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": "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.", + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", "value": "" }, { "key": "amenity", - "description": "Layer 'Charging stations' shows 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=&construction:amenity=&disused:amenity=&operational_status=&amenity=charging_station with a fixed text, namely 'This charging station works' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "broken" + }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=&operational_status=broken&amenity=charging_station with a fixed text, namely 'This charging station is broken' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", + "value": "" + }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=charging_station&construction:amenity=&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is planned here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", + "value": "" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", + "value": "charging_station" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key disused:amenity.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key operational_status.", + "value": "" + }, + { + "key": "amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=charging_station&disused:amenity=&operational_status=&amenity= with a fixed text, namely 'A charging station is constructed here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", + "value": "" + }, + { + "key": "planned:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key planned:amenity.", + "value": "" + }, + { + "key": "construction:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key construction:amenity.", + "value": "" + }, + { + "key": "disused:amenity", + "description": "Layer 'Charging stations' shows planned:amenity=&construction:amenity=&disused:amenity=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations')", "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 '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=charging_station&operational_status=&amenity= with a fixed text, namely 'This charging station has beed permanently disabled and is not in use anymore but is still visible' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Charging stations') Picking this answer will delete the key amenity.", "value": "" }, { 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..1bd972575 100644 --- a/Docs/TagInfo/mapcomplete_food.json +++ b/Docs/TagInfo/mapcomplete_food.json @@ -76,9 +76,19 @@ "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')", + "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Restaurants and fast food')", "value": "designated" }, { diff --git a/Docs/TagInfo/mapcomplete_fritures.json b/Docs/TagInfo/mapcomplete_fritures.json index ae5de3180..1f4161ac1 100644 --- a/Docs/TagInfo/mapcomplete_fritures.json +++ b/Docs/TagInfo/mapcomplete_fritures.json @@ -81,9 +81,19 @@ "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')", + "description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", "value": "designated" }, { @@ -396,9 +406,19 @@ "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')", + "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Friturenkaart')", "value": "designated" }, { diff --git a/Docs/TagInfo/mapcomplete_hackerspaces.json b/Docs/TagInfo/mapcomplete_hackerspaces.json index dad544320..bb607f4f3 100644 --- a/Docs/TagInfo/mapcomplete_hackerspaces.json +++ b/Docs/TagInfo/mapcomplete_hackerspaces.json @@ -52,7 +52,7 @@ }, { "key": "wheelchair", - "description": "Layer 'Hackerspace' 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 'Hackerspaces')", + "description": "Layer 'Hackerspace' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Hackerspaces')", "value": "designated" }, { 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_observation_towers.json b/Docs/TagInfo/mapcomplete_observation_towers.json index f7f7caa24..4a22ae603 100644 --- a/Docs/TagInfo/mapcomplete_observation_towers.json +++ b/Docs/TagInfo/mapcomplete_observation_towers.json @@ -88,7 +88,7 @@ }, { "key": "wheelchair", - "description": "Layer 'Observation towers' 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 'Observation towers')", + "description": "Layer 'Observation towers' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Observation towers')", "value": "designated" }, { diff --git a/Docs/TagInfo/mapcomplete_street_lighting.json b/Docs/TagInfo/mapcomplete_street_lighting.json new file mode 100644 index 000000000..9a3a67e87 --- /dev/null +++ b/Docs/TagInfo/mapcomplete_street_lighting.json @@ -0,0 +1,225 @@ +{ + "data_format": 1, + "project": { + "name": "MapComplete Street Lighting", + "description": "On this map you can find everything about street lighting", + "project_url": "https://mapcomplete.osm.be/street_lighting", + "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", + "icon_url": "https://mapcomplete.osm.be/assets/layers/street_lamps/street_lamp.svg", + "contact_name": "Pieter Vander Vennet, Robin van der Linde", + "contact_email": "pietervdvn@posteo.net" + }, + "tags": [ + { + "key": "highway", + "description": "The MapComplete theme Street Lighting has a layer Street Lamps showing features with this tag", + "value": "street_lamp" + }, + { + "key": "ref", + "description": "Layer 'Street Lamps' shows and asks freeform values for key 'ref' (in the MapComplete.osm.be theme 'Street Lighting')" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=catenary with a fixed text, namely 'This lamp is suspended using cables' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "catenary" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=ceiling with a fixed text, namely 'This lamp is mounted on a ceiling' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "ceiling" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=ground with a fixed text, namely 'This lamp is mounted in the ground' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "ground" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=pedestal with a fixed text, namely 'This lamp is mounted on a short pole (mostly < 1.5m)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "pedestal" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=pole with a fixed text, namely 'This lamp is mounted on a pole' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "pole" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=wall with a fixed text, namely 'This lamp is mounted directly to the wall' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "wall" + }, + { + "key": "support", + "description": "Layer 'Street Lamps' shows support=wall_mount with a fixed text, namely 'This lamp is mounted to the wall using a metal bar' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "wall_mount" + }, + { + "key": "lamp_mount", + "description": "Layer 'Street Lamps' shows lamp_mount=straight_mast with a fixed text, namely 'This lamp sits atop of a straight mast' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "straight_mast" + }, + { + "key": "lamp_mount", + "description": "Layer 'Street Lamps' shows lamp_mount=bent_mast with a fixed text, namely 'This lamp sits at the end of a bent mast' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "bent_mast" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=electric with a fixed text, namely 'This lamp is lit electrically' (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "electric" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=LED with a fixed text, namely 'This lamp uses LEDs' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "LED" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=incandescent with a fixed text, namely 'This lamp uses incandescent lighting' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "incandescent" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=halogen with a fixed text, namely 'This lamp uses halogen lighting' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "halogen" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=discharge with a fixed text, namely 'This lamp uses discharge lamps (unknown type)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "discharge" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=mercury with a fixed text, namely 'This lamp uses a mercury-vapour lamp (lightly blueish)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "mercury" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=metal-halide with a fixed text, namely 'This lamp uses metal-halide lamps (bright white)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "metal-halide" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=fluorescent with a fixed text, namely 'This lamp uses fluorescent lighting' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "fluorescent" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=sodium with a fixed text, namely 'This lamp uses sodium lamps (unknown type)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "sodium" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=low_pressure_sodium with a fixed text, namely 'This lamp uses low pressure sodium lamps (monochrome orange)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "low_pressure_sodium" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=high_pressure_sodium with a fixed text, namely 'This lamp uses high pressure sodium lamps (orange with white)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "high_pressure_sodium" + }, + { + "key": "light:method", + "description": "Layer 'Street Lamps' shows light:method=gas with a fixed text, namely 'This lamp is lit using gas' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "gas" + }, + { + "key": "light:colour", + "description": "Layer 'Street Lamps' shows and asks freeform values for key 'light:colour' (in the MapComplete.osm.be theme 'Street Lighting')" + }, + { + "key": "light:colour", + "description": "Layer 'Street Lamps' shows light:colour=white with a fixed text, namely 'This lamp emits white light' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "white" + }, + { + "key": "light:colour", + "description": "Layer 'Street Lamps' shows light:colour=green with a fixed text, namely 'This lamp emits green light' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "green" + }, + { + "key": "light:colour", + "description": "Layer 'Street Lamps' shows light:colour=orange with a fixed text, namely 'This lamp emits orange light' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "orange" + }, + { + "key": "light:count", + "description": "Layer 'Street Lamps' shows and asks freeform values for key 'light:count' (in the MapComplete.osm.be theme 'Street Lighting')" + }, + { + "key": "light:count", + "description": "Layer 'Street Lamps' shows light:count=1 with a fixed text, namely 'This lamp has 1 fixture' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "1" + }, + { + "key": "light:count", + "description": "Layer 'Street Lamps' shows light:count=2 with a fixed text, namely 'This lamp has 2 fixtures' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "2" + }, + { + "key": "light:lit", + "description": "Layer 'Street Lamps' shows light:lit=dusk-dawn with a fixed text, namely 'This lamp is lit at night' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "dusk-dawn" + }, + { + "key": "light:lit", + "description": "Layer 'Street Lamps' shows light:lit=24/7 with a fixed text, namely 'This lamp is lit 24/7' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "24/7" + }, + { + "key": "light:lit", + "description": "Layer 'Street Lamps' shows light:lit=motion with a fixed text, namely 'This lamp is lit based on motion' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "motion" + }, + { + "key": "light:lit", + "description": "Layer 'Street Lamps' shows light:lit=demand with a fixed text, namely 'This lamp is lit based on demand (e.g. with a pushbutton)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "demand" + }, + { + "key": "light:direction", + "description": "Layer 'Street Lamps' shows and asks freeform values for key 'light:direction' (in the MapComplete.osm.be theme 'Street Lighting')" + }, + { + "key": "lit", + "description": "Layer 'Lit streets' shows lit=yes with a fixed text, namely 'This street is lit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "yes" + }, + { + "key": "lit", + "description": "Layer 'Lit streets' shows lit=no with a fixed text, namely 'This street is not lit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "no" + }, + { + "key": "lit", + "description": "Layer 'Lit streets' shows lit=sunset-sunrise with a fixed text, namely 'This street is lit at night' (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "sunset-sunrise" + }, + { + "key": "lit", + "description": "Layer 'Lit streets' shows lit=24/7 with a fixed text, namely 'This street is lit 24/7' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "24/7" + }, + { + "key": "lit", + "description": "Layer 'All streets' shows lit=yes with a fixed text, namely 'This street is lit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "yes" + }, + { + "key": "lit", + "description": "Layer 'All streets' shows lit=no with a fixed text, namely 'This street is not lit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "no" + }, + { + "key": "lit", + "description": "Layer 'All streets' shows lit=sunset-sunrise with a fixed text, namely 'This street is lit at night' (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "sunset-sunrise" + }, + { + "key": "lit", + "description": "Layer 'All streets' shows lit=24/7 with a fixed text, namely 'This street is lit 24/7' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Street Lighting')", + "value": "24/7" + } + ] +} \ 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/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..c508f8902 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,10 +58,15 @@ export default class GeoLocationHandler extends VariableUiElement { private readonly _layoutToUse: LayoutConfig; constructor( - currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, - leafletMap: UIEventSource, - layoutToUse: LayoutConfig + state: { + currentGPSLocation: UIEventSource, + leafletMap: UIEventSource, + layoutToUse: LayoutConfig, + featureSwitchGeolocation: UIEventSource + } ) { + const currentGPSLocation = state.currentGPSLocation + const leafletMap = state.leafletMap const hasLocation = currentGPSLocation.map( (location) => location !== undefined ); @@ -127,7 +127,7 @@ export default class GeoLocationHandler extends VariableUiElement { this._previousLocationGrant = previousLocationGrant; this._currentGPSLocation = currentGPSLocation; this._leafletMap = leafletMap; - this._layoutToUse = layoutToUse; + this._layoutToUse = state.layoutToUse; this._hasLocation = hasLocation; const self = this; @@ -172,7 +172,7 @@ export default class GeoLocationHandler extends VariableUiElement { const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon") - this.init(false, !latLonGiven); + this.init(false, !latLonGiven && state.featureSwitchGeolocation.data); isLocked.addCallbackAndRunD(isLocked => { if (isLocked) { @@ -182,10 +182,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,36 +209,11 @@ 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) { + private init(askPermission: boolean, zoomToLocation: boolean) { const self = this; if (self._isActive.data) { @@ -237,7 +227,7 @@ export default class GeoLocationHandler extends VariableUiElement { ?.then(function (status) { console.log("Geolocation permission is ", status.state); if (status.state === "granted") { - self.StartGeolocating(forceZoom); + self.StartGeolocating(zoomToLocation); } self._permission.setData(status.state); status.onchange = function () { @@ -249,10 +239,10 @@ export default class GeoLocationHandler extends VariableUiElement { } if (askPermission) { - self.StartGeolocating(forceZoom); + self.StartGeolocating(zoomToLocation); } else if (this._previousLocationGrant.data === "granted") { this._previousLocationGrant.setData(""); - self.StartGeolocating(forceZoom); + self.StartGeolocating(zoomToLocation); } } @@ -261,8 +251,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 +265,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 +304,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 78634897b..ccd320125 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -116,6 +116,11 @@ 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 { diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts index 476131732..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 { @@ -74,6 +75,7 @@ export default class DetermineLayout { const parsed = await Utils.downloadJson(link) console.log("Got ", parsed) + LegacyJsonConvert.fixThemeConfig(parsed) try { parsed.id = link; return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed)); @@ -87,7 +89,7 @@ export default class DetermineLayout { } } catch (e) { - console.erorr(e) + console.error(e) DetermineLayout.ShowErrorOnCustomTheme( `${link} is invalid - probably not found or invalid JSON:`, new FixedUiElement(e) @@ -136,6 +138,7 @@ export default class DetermineLayout { } } + LegacyJsonConvert.fixThemeConfig(json) const layoutToUse = new LayoutConfig(json, false); userLayoutParam.setData(layoutToUse.id); return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index 8f0265bb1..a3e12567f 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -54,16 +54,17 @@ export class ExtraFunction { private static readonly OverlapFunc = new ExtraFunction( { name: "overlapWith", - 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. " + + doc: "Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well." + + "If the current feature is a point, all features that this point is embeded in are given.\n\n" + "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" + "\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[]; @@ -200,7 +233,7 @@ export class ExtraFunction { return new Combine([ ExtraFunction.intro, - new List(ExtraFunction.allFuncs.map(func => func._name)), + new List(ExtraFunction.allFuncs.map(func => `[${func._name}](#${func._name})`)), ...elems ]); } @@ -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 8cd9aae67..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; @@ -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 @@ -221,6 +226,18 @@ export default class FeaturePipeline { 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) @@ -266,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() }) @@ -391,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, @@ -412,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/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/FullNodeDatabaseSource.ts b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts new file mode 100644 index 000000000..750f6d037 --- /dev/null +++ b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts @@ -0,0 +1,67 @@ +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"; + + +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" + } + } + + 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) + + } + + +} + 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 09a288117..a4fd2ea35 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -37,8 +37,10 @@ export class GeoOperations { * The features with which 'feature' overlaps, are returned together with their overlap area in m² * * If 'feature' is a LineString, the features in which this feature is (partly) embedded is returned, the overlap length in meter is given + * If 'feature' is a Polygon, overlapping points and points within the polygon will be returned * * If 'feature' is a point, it will return every feature the point is embedded in. Overlap will be undefined + * */ static calculateOverlap(feature: any, otherFeatures: any[]): { feat: any, overlap: number }[] { @@ -235,6 +237,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"}); } @@ -297,7 +306,7 @@ export class GeoOperations { return [x, y]; } -//Converts XY point from (Spherical) Web Mercator EPSG:3785 (unofficially EPSG:900913) to lat/lon in WGS84 Datum + //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] @@ -403,6 +412,31 @@ export class GeoOperations { return undefined; } + /** + * Tries to remove points which do not contribute much to the general outline. + * Points for which the angle is ~ 180° are removed + * @param coordinates + * @constructor + */ + public static SimplifyCoordinates(coordinates: [number, number][]){ + const newCoordinates = [] + for (let i = 1; i < coordinates.length - 1; i++){ + const coordinate = coordinates[i]; + const prev = coordinates[i - 1] + const next = coordinates[i + 1] + const b0 = turf.bearing(prev, coordinate, {final: true}) + const b1 = turf.bearing(coordinate, next) + + const diff = Math.abs(b1 - b0) + if(diff < 2){ + continue + } + newCoordinates.push(coordinate) + } + return newCoordinates + + } + } 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/CreateWayWithPointReuseAction.ts b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts new file mode 100644 index 000000000..a411bee82 --- /dev/null +++ b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts @@ -0,0 +1,313 @@ +import OsmChangeAction from "./OsmChangeAction"; +import {Tag} from "../../Tags/Tag"; +import {Changes} from "../Changes"; +import {ChangeDescription} from "./ChangeDescription"; +import FeaturePipelineState from "../../State/FeaturePipelineState"; +import {BBox} from "../../BBox"; +import {TagsFilter} from "../../Tags/TagsFilter"; +import {GeoOperations} from "../../GeoOperations"; +import FeatureSource from "../../FeatureSource/FeatureSource"; +import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"; +import CreateNewNodeAction from "./CreateNewNodeAction"; +import CreateNewWayAction from "./CreateNewWayAction"; + + +export interface MergePointConfig { + withinRangeOfM: number, + ifMatches: TagsFilter, + mode: "reuse_osm_point" | "move_osm_point" +} + +interface CoordinateInfo { + lngLat: [number, number], + identicalTo?: number, + closebyNodes?: { + d: number, + node: any, + config: MergePointConfig + }[] +} + +/** + * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points + */ +export default class CreateWayWithPointReuseAction extends OsmChangeAction { + private readonly _tags: Tag[]; + /** + * lngLat-coordinates + * @private + */ + private _coordinateInfo: CoordinateInfo[]; + private _state: FeaturePipelineState; + private _config: MergePointConfig[]; + + constructor(tags: Tag[], + coordinates: [number, number][], + state: FeaturePipelineState, + config: MergePointConfig[] + ) { + super(); + this._tags = tags; + this._state = state; + this._config = config; + this._coordinateInfo = this.CalculateClosebyNodes(coordinates); + + } + + public async getPreview(): Promise { + + const features = [] + let geometryMoved = false; + for (let i = 0; i < this._coordinateInfo.length; i++) { + const coordinateInfo = this._coordinateInfo[i]; + if (coordinateInfo.identicalTo !== undefined) { + continue + } + if (coordinateInfo.closebyNodes === undefined || coordinateInfo.closebyNodes.length === 0) { + + const newPoint = { + type: "Feature", + properties: { + "newpoint": "yes", + id: "new-geometry-with-reuse-" + i + }, + geometry: { + type: "Point", + coordinates: coordinateInfo.lngLat + } + }; + features.push(newPoint) + continue + } + + const reusedPoint = coordinateInfo.closebyNodes[0] + if (reusedPoint.config.mode === "move_osm_point") { + const moveDescription = { + type: "Feature", + properties: { + "move": "yes", + "osm-id": reusedPoint.node.properties.id, + "id": "new-geometry-move-existing" + i + }, + geometry: { + type: "LineString", + coordinates: [reusedPoint.node.geometry.coordinates, coordinateInfo.lngLat] + } + } + features.push(moveDescription) + + } else { + // The geometry is moved + geometryMoved = true + } + } + + if (geometryMoved) { + + const coords: [number, number][] = [] + for (const info of this._coordinateInfo) { + if (info.identicalTo !== undefined) { + coords.push(coords[info.identicalTo]) + continue + } + + if (info.closebyNodes === undefined || info.closebyNodes.length === 0) { + coords.push(coords[info.identicalTo]) + continue + } + + const closest = info.closebyNodes[0] + if (closest.config.mode === "reuse_osm_point") { + coords.push(closest.node.geometry.coordinates) + } else { + coords.push(info.lngLat) + } + + } + const newGeometry = { + type: "Feature", + properties: { + "resulting-geometry": "yes", + "id": "new-geometry" + }, + geometry: { + type: "LineString", + coordinates: coords + } + } + features.push(newGeometry) + + } + console.log("Preview:", features) + return new StaticFeatureSource(features, false) + } + + protected async CreateChangeDescriptions(changes: Changes): Promise { + const theme = this._state.layoutToUse.id + const allChanges: ChangeDescription[] = [] + const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] + for (let i = 0; i < this._coordinateInfo.length; i++) { + const info = this._coordinateInfo[i] + const lat = info.lngLat[1] + const lon = info.lngLat[0] + + if (info.identicalTo !== undefined) { + nodeIdsToUse.push(nodeIdsToUse[info.identicalTo]) + continue + } + if (info.closebyNodes === undefined || info.closebyNodes[0] === undefined) { + const newNodeAction = new CreateNewNodeAction([], lat, lon, { + allowReuseOfPreviouslyCreatedPoints: true, + changeType: null, + theme + }) + + allChanges.push(...(await newNodeAction.CreateChangeDescriptions(changes))) + + nodeIdsToUse.push({ + lat, lon, + nodeId : newNodeAction.newElementIdNumber}) + continue + + } + + const closestPoint = info.closebyNodes[0] + const id = Number(closestPoint.node.properties.id.split("/")[1]) + if(closestPoint.config.mode === "move_osm_point"){ + allChanges.push({ + type: "node", + id, + changes: { + lat, lon + }, + meta: { + theme, + changeType: null + } + }) + } + nodeIdsToUse.push({lat, lon, nodeId: id}) + } + + + const newWay = new CreateNewWayAction(this._tags, nodeIdsToUse, { + theme + }) + + allChanges.push(...(await newWay.Perform(changes))) + + return allChanges + } + + private CalculateClosebyNodes(coordinates: [number, number][]): CoordinateInfo[] { + + const bbox = new BBox(coordinates) + const state = this._state + const allNodes = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox.pad(1.2))) + const maxDistance = Math.max(...this._config.map(c => c.withinRangeOfM)) + + const coordinateInfo: { + lngLat: [number, number], + identicalTo?: number, + closebyNodes?: { + d: number, + node: any, + config: MergePointConfig + }[] + }[] = coordinates.map(_ => undefined) + + for (let i = 0; i < coordinates.length; i++) { + + if (coordinateInfo[i] !== undefined) { + // Already seen, probably a duplicate coordinate + continue + } + const coor = coordinates[i] + // Check closeby (and probably identical) point further in the coordinate list, mark them as duplicate + for (let j = i + 1; j < coordinates.length; j++) { + if (1000 * GeoOperations.distanceBetween(coor, coordinates[j]) < 0.1) { + coordinateInfo[j] = { + lngLat: coor, + identicalTo: i + } + break; + } + } + + // Gather the actual info for this point + + // Lets search applicable points and determine the merge mode + const closebyNodes: { + d: number, + node: any, + config: MergePointConfig + }[] = [] + for (const node of allNodes) { + const center = node.geometry.coordinates + const d = 1000 * GeoOperations.distanceBetween(coor, center) + if (d > maxDistance) { + continue + } + + for (const config of this._config) { + if (d > config.withinRangeOfM) { + continue + } + if (!config.ifMatches.matchesProperties(node.properties)) { + continue + } + closebyNodes.push({node, d, config}) + } + } + + closebyNodes.sort((n0, n1) => { + return n0.d - n1.d + }) + + coordinateInfo[i] = { + identicalTo: undefined, + lngLat: coor, + closebyNodes + } + + } + + let conflictFree = true; + + do { + conflictFree = true; + for (let i = 0; i < coordinateInfo.length; i++) { + + const coorInfo = coordinateInfo[i] + if (coorInfo.identicalTo !== undefined) { + continue + } + if (coorInfo.closebyNodes === undefined || coorInfo.closebyNodes[0] === undefined) { + continue + } + + for (let j = i + 1; j < coordinates.length; j++) { + const other = coordinateInfo[j] + if (other.closebyNodes === undefined || other.closebyNodes[0] === undefined) { + continue + } + + if (other.closebyNodes[0].node === coorInfo.closebyNodes[0].node) { + conflictFree = false + // We have found a conflict! + // We only keep the closest point + if (other.closebyNodes[0].d > coorInfo.closebyNodes[0].d) { + other.closebyNodes.shift() + } else { + coorInfo.closebyNodes.shift() + } + } + } + } + } while (!conflictFree) + + + return coordinateInfo + } + +} \ 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..cc56ff03e --- /dev/null +++ b/Logic/Osm/Actions/ReplaceGeometryAction.ts @@ -0,0 +1,278 @@ +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; + /** + * The target coordinates that should end up in OpenStreetMap + */ + private readonly targetCoordinates: [number, number][]; + /** + * If a target coordinate is close to another target coordinate, 'identicalTo' will point to the first index. + * @private + */ + private readonly identicalTo: 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.identicalTo = coordinates.map(_ => undefined) + + for (let i = 0; i < coordinates.length; i++) { + if (this.identicalTo[i] !== undefined) { + continue + } + for (let j = i + 1; j < coordinates.length; j++) { + const d = 1000 * GeoOperations.distanceBetween(coordinates[i], coordinates[j]) + if (d < 0.1) { + console.log("Identical coordinates detected: ", i, " and ", j, ": ", coordinates[i], coordinates[j], "distance is", d) + this.identicalTo[j] = i + } + } + } + + + this.targetCoordinates = coordinates + this.newTags = options.newTags + } + + public async getPreview(): Promise { + const {closestIds, allNodesById} = await this.GetClosestIds(); + console.log("Generating preview, identicals are ", ) + const preview = closestIds.map((newId, i) => { + if(this.identicalTo[i] !== undefined){ + return undefined + } + + + 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(Utils.NoNull(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++) { + if(this.identicalTo[i] !== undefined){ + const j = this.identicalTo[i] + actualIdsToUse.push(actualIdsToUse[j]) + continue + } + 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: detect intersections with other ways if moved + 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 (let i = 0; i < this.targetCoordinates.length; i++){ + const target = this.targetCoordinates[i]; + 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 + // We skip the ones which are identical + console.log("Erasing double ids") + for (let i = 0; i < closestIds.length; i++) { + if(this.identicalTo[i] !== undefined){ + closestIds[i] = closestIds[this.identicalTo[i]] + continue + } + const closestId = closestIds[i] + for (let j = i + 1; j < closestIds.length; j++) { + if(this.identicalTo[j] !== undefined){ + continue + } + 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 59857d108..03e6aede0 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.3"; + public static vNumber = "0.12.2-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 c15c90d4a..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 @@ -126,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. @@ -265,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 58e112f4c..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. @@ -169,7 +175,6 @@ export interface TagRenderingConfigJson { */ ifnot?: AndOrTagConfigJson | string - /** * If chosen as answer, these tags will be applied as well onto the object. * Not compatible with multiAnswer @@ -177,10 +182,4 @@ export interface TagRenderingConfigJson { 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 9f49a6c21..5c8090d61 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,54 +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"], - mercatorCrs: json.source["mercatorCrs"] - }, - this.id - ); - } else { - this.source = new SourceConfig({ - osmTags: legacy, - }); - } - this.calculatedTags = undefined; if (json.calculatedTags !== undefined) { if (!official) { @@ -163,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 + ")" } @@ -209,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["location"] !== 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["location"] === 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) @@ -336,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) { @@ -401,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); @@ -660,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 54e2a6454..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[] { @@ -308,5 +287,9 @@ export default class LayoutConfig { }) 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..7f7b0e30f --- /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["id"] === undefined) { + + if (tagRendering["#"] !== undefined) { + tagRendering["id"] = tagRendering["#"] + delete tagRendering["#"] + } else 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..911a2ed54 --- /dev/null +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -0,0 +1,267 @@ +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(json.icon === undefined && json.label === undefined){ + throw `A point rendering should define at least an icon or a label` + } + + 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 = Utils.SubstituteKeys(self.rotation?.GetRenderValue(tags)?.txt ?? "0deg", tags) + + 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/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 0b0e80437..7ef2c5fec 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -15,6 +15,7 @@ import {Tag} from "../../Logic/Tags/Tag"; export default class TagRenderingConfig { readonly id: string; + readonly group: string; readonly render?: Translation; readonly question?: Translation; readonly condition?: TagsFilter; @@ -39,9 +40,8 @@ export default class TagRenderingConfig { 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 @@ -49,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; } @@ -61,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){ @@ -149,7 +148,7 @@ export default class TagRenderingConfig { const mp = { if: TagUtils.Tag(mapping.if, `${ctx}.if`), ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined), - then: Translations.T(mapping.then, `{mappingContext}.then`), + then: Translations.T(mapping.then, `${ctx}.then`), hideInAnswer: hideInAnswer, addExtraTags: (mapping.addExtraTags??[]).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`)) }; @@ -229,7 +228,6 @@ export default class TagRenderingConfig { } } - /** * Returns true if it is known or not shown, false if the question should be asked * @constructor @@ -262,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' @@ -311,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) { @@ -331,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/AllThemesGui.ts b/UI/AllThemesGui.ts index be4e0d6e4..ec5e2481d 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -7,11 +7,14 @@ import UserRelatedState from "../Logic/State/UserRelatedState"; import {Utils} from "../Utils"; import LanguagePicker from "./LanguagePicker"; import IndexText from "./BigComponents/IndexText"; -import {feature} from "@turf/turf"; import FeaturedMessage from "./BigComponents/FeaturedMessage"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; export default class AllThemesGui { constructor() { + + try{ + new FixedUiElement("").AttachTo("centermessage") const state = new UserRelatedState(undefined); const intro = new Combine([ @@ -30,5 +33,9 @@ export default class AllThemesGui { ]).SetClass("block m-5 lg:w-3/4 lg:ml-40") .SetStyle("pointer-events: all;") .AttachTo("topleft-tools"); + }catch (e) { + new FixedUiElement("Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!").SetClass("alert") + .AttachTo("centermessage") + } } -} \ No newline at end of file +} 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 59ca6e0c9..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 }[] { @@ -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..5175132d9 100644 --- a/UI/BigComponents/ImportButton.ts +++ b/UI/BigComponents/ImportButton.ts @@ -4,27 +4,212 @@ 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"; +import FullNodeDatabaseSource from "../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import CreateWayWithPointReuseAction from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction"; +import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction"; +import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; + + +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 +217,232 @@ 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: true, + 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 + }) + + let action : OsmChangeAction & {getPreview() : Promise} + + const theme = o.state.layoutToUse.id + const changes = o.state.changes + let confirm: () => Promise + if (o.conflationSettings !== undefined) { + + action = new ReplaceGeometryAction( + o.state, + o.feature, + o.conflationSettings.conflateWayId, + { + theme: o.state.layoutToUse.id, + newTags: o.newTags.data + } + ) + + confirm = async () => { + changes.applyAction (action) + return o.feature.properties.id + } + + } else { + 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] + } + + + action = new CreateWayWithPointReuseAction( + o.newTags.data, + coordinates, + // @ts-ignore + o.state, + [{ + withinRangeOfM: 1, + ifMatches: new Tag("_is_part_of_building","true"), + mode:"move_osm_point" + + }] + ) + + + confirm = async () => { + changes.applyAction(action) + return undefined + } + } + + + action.getPreview().then(changePreview => { + new ShowDataLayer({ + leafletMap: confirmationMap.leafletMap, + enablePopups: false, + zoomToFeatures: false, + features: changePreview, + allElements: o.state.allElements, + layerToShow: AllKnownLayers.sharedLayers.get("conflation") + }) + }) + + 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..35f11754c 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -4,17 +4,28 @@ 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 + ) + + 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 b112b15b0..9fc7dde91 100644 --- a/UI/BigComponents/ShareScreen.ts +++ b/UI/BigComponents/ShareScreen.ts @@ -8,11 +8,14 @@ import Toggle from "../Input/Toggle"; import Translations from "../i18n/Translations"; import BaseUIElement from "../BaseUIElement"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; -import MapState from "../../Logic/State/MapState"; +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(state: MapState) { + constructor(state: {layoutToUse: LayoutConfig, locationControl: UIEventSource, backgroundLayer: UIEventSource, filteredLayers: UIEventSource}) { const layout = state?.layoutToUse; const tr = Translations.t.general.sharescreen; 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 1f1326246..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( @@ -107,7 +137,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { 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 533017447..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"; @@ -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", 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 680b3e571..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 c26544d4e..857dbe9d9 100644 --- a/assets/layers/artwork/artwork.json +++ b/assets/layers/artwork/artwork.json @@ -29,13 +29,13 @@ "ja": "アートワーク", "zh_Hant": "藝術品", "nb_NO": "Kunstverk", - "fi": "Taideteos", - "gl": "Obra de arte", - "hu": "Műalkotás", - "pl": "Dzieło sztuki", - "pt": "Obra de arte", + "sv": "Konstverk", "pt_BR": "Obra de arte", - "sv": "Konstverk" + "pt": "Obra de arte", + "pl": "Dzieło sztuki", + "hu": "Műalkotás", + "gl": "Obra de arte", + "fi": "Taideteos" }, "mappings": [ { @@ -417,5 +417,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 666c290e0..01f2b580c 100644 --- a/assets/layers/barrier/barrier.json +++ b/assets/layers/barrier/barrier.json @@ -324,5 +324,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 b1d36cbcc..89d567abb 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -660,5 +660,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 845106a71..692ee8a3c 100644 --- a/assets/layers/bench_at_pt/bench_at_pt.json +++ b/assets/layers/bench_at_pt/bench_at_pt.json @@ -149,5 +149,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 678924b39..a1465e55c 100644 --- a/assets/layers/bicycle_library/bicycle_library.json +++ b/assets/layers/bicycle_library/bicycle_library.json @@ -288,5 +288,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 2158aa5bb..3d78f3b2d 100644 --- a/assets/layers/bike_parking/bike_parking.json +++ b/assets/layers/bike_parking/bike_parking.json @@ -539,4 +539,6 @@ "width": "1" } ] + } + diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index a166727c4..d9c34b759 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -759,5 +759,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 1dca15720..53b3f8c48 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -1,2195 +1,2307 @@ { - "id": "charging_station", - "name": { - "en": "Charging stations", - "nl": "Oplaadpunten" - }, - "minzoom": 10, - "source": { - "osmTags": { - "or": [ - "amenity=charging_station", - "disused:amenity=charging_station", - "planned:amenity=charging_station", - "construction:amenity=charging_station" - ] - } - }, - "title": { - "render": { - "en": "Charging station", - "nl": "Oplaadpunten" - } - }, - "description": { - "en": "A charging station", - "nl": "Oplaadpunten" - }, - "tagRenderings": [ - "images", - { - "id": "Type", - "#": "Allowed vehicle types", - "question": { - "en": "Which vehicles are allowed to charge here?", - "nl": "Welke voertuigen kunnen hier opgeladen worden?" - }, - "multiAnswer": true, - "mappings": [ - { - "if": "bicycle=yes", - "ifnot": "bicycle=no", - "then": { - "en": "Bcycles can be charged here", - "nl": "Fietsen kunnen hier opgeladen worden" - } - }, - { - "if": "motorcar=yes", - "ifnot": "motorcar=no", - "then": { - "en": "Cars can be charged here", - "nl": "Elektrische auto's kunnen hier opgeladen worden" - } - }, - { - "if": "scooter=yes", - "ifnot": "scooter=no", - "then": { - "en": "Scooters can be charged here", - "nl": "Electrische scooters (snorfiets of bromfiets) kunnen hier opgeladen worden" - } - }, - { - "if": "hgv=yes", - "ifnot": "hgv=no", - "then": { - "en": "Heavy good vehicles (such as trucks) can be charged here", - "nl": "Vrachtwagens kunnen hier opgeladen worden" - } - }, - { - "if": "bus=yes", - "ifnot": "bus=no", - "then": { - "en": "Buses can be charged here", - "nl": "Bussen kunnen hier opgeladen worden" - } + "id": "charging_station", + "name": { + "en": "Charging stations", + "nl": "Oplaadpunten" + }, + "minzoom": 10, + "source": { + "osmTags": { + "or": [ + "amenity=charging_station", + "disused:amenity=charging_station", + "planned:amenity=charging_station", + "construction:amenity=charging_station" + ] } - ] }, - { - "id": "access", - "question": { - "en": "Who is allowed to use this charging station?", - "nl": "Wie mag er dit oplaadpunt gebruiken?" - }, - "render": { - "en": "Access is {access}", - "nl": "Toegang voor {access}" - }, - "freeform": { - "key": "access", - "addExtraTags": [ - "fixme=Freeform field used for access - doublecheck the value" - ] - }, - "mappings": [ - { - "if": "access=yes", - "then": { - "en": "Anyone can use this charging station (payment might be needed)", - "nl": "Toegankelijk voor iedereen (mogelijks met aanmelden en/of te betalen)" - } - }, - { - "if": { - "or": [ - "access=permissive", - "access=public" - ] - }, - "then": { - "en": "Anyone can use this charging station (payment might be needed)", - "nl": "Toegankelijk voor iedereen (mogelijks met aanmelden en/of te betalen)" - }, - "hideInAnswer": true - }, - { - "if": "access=customers", - "then": { - "en": "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", - "nl": "Enkel klanten van de bijhorende plaats mogen dit oplaadpunt gebruiken
Bv. op de parking van een hotel en enkel toegankelijk voor klanten van dit hotel" - } - }, - { - "if": "access=private", - "then": { - "en": "Not accessible to the general public (e.g. only accessible to the owners, employees, ...)", - "nl": "Niet toegankelijk voor het publiek
Bv. enkel toegankelijk voor de eigenaar, medewerkers ,... " - } + "title": { + "render": { + "en": "Charging station", + "nl": "Oplaadpunten" } - ] }, - { - "id": "capacity", - "render": { - "en": "{capacity} vehicles can be charged here at the same time", - "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden" - }, - "question": { - "en": "How much vehicles can be charged here at the same time?", - "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?" - }, - "freeform": { - "key": "capacity", - "type": "pnat" - } + "description": { + "en": "A charging station", + "nl": "Oplaadpunten" }, - { - "id": "Available_charging_stations (generated)", - "question": { - "en": "Which charging connections are available here?", - "nl": "Welke aansluitingen zijn hier beschikbaar?" - }, - "multiAnswer": true, - "mappings": [ + "tagRenderings": [ + "images", { - "if": "socket:schuko=1", - "ifnot": "socket:schuko=", - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "hideInAnswer": { - "or": [ - "_country!=be", - "_country!=fr", - "_country!=ma", - "_country!=tn", - "_country!=pl", - "_country!=cs", - "_country!=sk", - "_country!=mo" + "id": "Type", + "#": "Allowed vehicle types", + "question": { + "en": "Which vehicles are allowed to charge here?", + "nl": "Welke voertuigen kunnen hier opgeladen worden?", + "de": "Welche Fahrzeuge dürfen hier geladen werden?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "bicycle=yes", + "ifnot": "bicycle=no", + "then": { + "en": "Bcycles can be charged here", + "nl": "Fietsen kunnen hier opgeladen worden", + "de": "Fahrräder können hier geladen werden" + } + }, + { + "if": "motorcar=yes", + "ifnot": "motorcar=no", + "then": { + "en": "Cars can be charged here", + "nl": "Elektrische auto's kunnen hier opgeladen worden", + "de": "Autos können hier geladen werden" + } + }, + { + "if": "scooter=yes", + "ifnot": "scooter=no", + "then": { + "en": "Scooters can be charged here", + "nl": "Electrische scooters (snorfiets of bromfiets) kunnen hier opgeladen worden", + "de": " Roller können hier geladen werden" + } + }, + { + "if": "hgv=yes", + "ifnot": "hgv=no", + "then": { + "en": "Heavy good vehicles (such as trucks) can be charged here", + "nl": "Vrachtwagens kunnen hier opgeladen worden", + "de": "Lastkraftwagen (LKW) können hier geladen werden" + } + }, + { + "if": "bus=yes", + "ifnot": "bus=no", + "then": { + "en": "Buses can be charged here", + "nl": "Bussen kunnen hier opgeladen worden", + "de": "Busse können hier geladen werden" + } + } ] - } }, { - "if": { - "and": [ - "socket:schuko~*", - "socket:schuko!=1" - ] - }, - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:typee=1", - "ifnot": "socket:typee=", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" - } - }, - { - "if": { - "and": [ - "socket:typee~*", - "socket:typee!=1" - ] - }, - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:chademo=1", - "ifnot": "socket:chademo=", - "then": { - "en": "
Chademo
", - "nl": "
Chademo
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" + "id": "access", + "question": { + "en": "Who is allowed to use this charging station?", + "nl": "Wie mag er dit oplaadpunt gebruiken?", + "de": "Wer darf diese Ladestation benutzen?" + }, + "render": { + "en": "Access is {access}", + "nl": "Toegang voor {access}", + "de": "Zugang ist {access}" + }, + "freeform": { + "key": "access", + "addExtraTags": [ + "fixme=Freeform field used for access - doublecheck the value" ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } + }, + "mappings": [ + { + "if": "access=yes", + "then": { + "en": "Anyone can use this charging station (payment might be needed)", + "nl": "Toegankelijk voor iedereen (mogelijks met aanmelden en/of te betalen)" + } + }, + { + "if": { + "or": [ + "access=permissive", + "access=public" + ] + }, + "then": { + "en": "Anyone can use this charging station (payment might be needed)", + "nl": "Toegankelijk voor iedereen (mogelijks met aanmelden en/of te betalen)" + }, + "hideInAnswer": true + }, + { + "if": "access=customers", + "then": { + "en": "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", + "nl": "Enkel klanten van de bijhorende plaats mogen dit oplaadpunt gebruiken
Bv. op de parking van een hotel en enkel toegankelijk voor klanten van dit hotel" + } + }, + { + "if": "access=private", + "then": { + "en": "Not accessible to the general public (e.g. only accessible to the owners, employees, ...)", + "nl": "Niet toegankelijk voor het publiek
Bv. enkel toegankelijk voor de eigenaar, medewerkers ,... " + } + } ] - } }, { - "if": { - "and": [ - "socket:chademo~*", - "socket:chademo!=1" - ] - }, - "then": { - "en": "
Chademo
", - "nl": "
Chademo
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1_cable=1", - "ifnot": "socket:type1_cable=", - "then": { - "en": "
Type 1 with cable (J1772)
", - "nl": "
Type 1 met kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=1" - ] - }, - "then": { - "en": "
Type 1 with cable (J1772)
", - "nl": "
Type 1 met kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1=1", - "ifnot": "socket:type1=", - "then": { - "en": "
Type 1 without cable (J1772)
", - "nl": "
Type 1 zonder kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1~*", - "socket:type1!=1" - ] - }, - "then": { - "en": "
Type 1 without cable (J1772)
", - "nl": "
Type 1 zonder kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1_combo=1", - "ifnot": "socket:type1_combo=", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=1" - ] - }, - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_supercharger=1", - "ifnot": "socket:tesla_supercharger=", - "then": { - "en": "
Tesla Supercharger
", - "nl": "
Tesla Supercharger
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger
", - "nl": "
Tesla Supercharger
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2=1", - "ifnot": "socket:type2=", - "then": { - "en": "
Type 2 (mennekes)
", - "nl": "
Type 2 (mennekes)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2~*", - "socket:type2!=1" - ] - }, - "then": { - "en": "
Type 2 (mennekes)
", - "nl": "
Type 2 (mennekes)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2_combo=1", - "ifnot": "socket:type2_combo=", - "then": { - "en": "
Type 2 CCS (mennekes)
", - "nl": "
Type 2 CCS (mennekes)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=1" - ] - }, - "then": { - "en": "
Type 2 CCS (mennekes)
", - "nl": "
Type 2 CCS (mennekes)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2_cable=1", - "ifnot": "socket:type2_cable=", - "then": { - "en": "
Type 2 with cable (mennekes)
", - "nl": "
Type 2 met kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=1" - ] - }, - "then": { - "en": "
Type 2 with cable (mennekes)
", - "nl": "
Type 2 met kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_supercharger_ccs=1", - "ifnot": "socket:tesla_supercharger_ccs=", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_destination=1", - "ifnot": "socket:tesla_destination=", - "then": { - "en": "
Tesla Supercharger (destination)
", - "nl": "
Tesla Supercharger (destination)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - }, - { - "or": [ - "_country!=us" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger (destination)
", - "nl": "
Tesla Supercharger (destination)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_destination=1", - "ifnot": "socket:tesla_destination=", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - }, - { - "or": [ - "_country=us" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=1" - ] - }, - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:USB-A=1", - "ifnot": "socket:USB-A=", - "then": { - "en": "
USB to charge phones and small electronics
", - "nl": "
USB om GSMs en kleine electronica op te laden
" - } - }, - { - "if": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=1" - ] - }, - "then": { - "en": "
USB to charge phones and small electronics
", - "nl": "
USB om GSMs en kleine electronica op te laden
" - }, - "hideInAnswer": true - }, - { - "if": "socket:bosch_3pin=1", - "ifnot": "socket:bosch_3pin=", - "then": { - "en": "
Bosch Active Connect with 3 pins and cable
", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "bicycle=no" - ] - }, - { - "and": [ - { - "or": [ - "car=yes", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] - }, - "bicycle!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=1" - ] - }, - "then": { - "en": "
Bosch Active Connect with 3 pins and cable
", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "hideInAnswer": true - }, - { - "if": "socket:bosch_5pin=1", - "ifnot": "socket:bosch_5pin=", - "then": { - "en": "
Bosch Active Connect with 5 pins and cable
", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "bicycle=no" - ] - }, - { - "and": [ - { - "or": [ - "car=yes", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] - }, - "bicycle!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=1" - ] - }, - "then": { - "en": "
Bosch Active Connect with 5 pins and cable
", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "hideInAnswer": true - } - ] - }, - { - "id": "plugs-0", - "question": { - "en": "How much plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
are available here?", - "nl": "Hoeveel stekkers van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:schuko} plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
available here", - "nl": "Hier zijn {socket:schuko} stekkers van het type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "freeform": { - "key": "socket:schuko", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:schuko~*", - "socket:schuko!=0" - ] - } - }, - { - "id": "plugs-1", - "question": { - "en": "How much plugs of type
European wall plug with ground pin (CEE7/4 type E)
are available here?", - "nl": "Hoeveel stekkers van type
Europese stekker met aardingspin (CEE7/4 type E)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:typee} plugs of type
European wall plug with ground pin (CEE7/4 type E)
available here", - "nl": "Hier zijn {socket:typee} stekkers van het type
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "freeform": { - "key": "socket:typee", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:typee~*", - "socket:typee!=0" - ] - } - }, - { - "id": "plugs-2", - "question": { - "en": "How much plugs of type
Chademo
are available here?", - "nl": "Hoeveel stekkers van type
Chademo
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:chademo} plugs of type
Chademo
available here", - "nl": "Hier zijn {socket:chademo} stekkers van het type
Chademo
" - }, - "freeform": { - "key": "socket:chademo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:chademo~*", - "socket:chademo!=0" - ] - } - }, - { - "id": "plugs-3", - "question": { - "en": "How much plugs of type
Type 1 with cable (J1772)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 met kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1_cable} plugs of type
Type 1 with cable (J1772)
available here", - "nl": "Hier zijn {socket:type1_cable} stekkers van het type
Type 1 met kabel (J1772)
" - }, - "freeform": { - "key": "socket:type1_cable", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=0" - ] - } - }, - { - "id": "plugs-4", - "question": { - "en": "How much plugs of type
Type 1 without cable (J1772)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 zonder kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1} plugs of type
Type 1 without cable (J1772)
available here", - "nl": "Hier zijn {socket:type1} stekkers van het type
Type 1 zonder kabel (J1772)
" - }, - "freeform": { - "key": "socket:type1", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1~*", - "socket:type1!=0" - ] - } - }, - { - "id": "plugs-5", - "question": { - "en": "How much plugs of type
Type 1 CCS (aka Type 1 Combo)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 CCS (ook gekend als Type 1 Combo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1_combo} plugs of type
Type 1 CCS (aka Type 1 Combo)
available here", - "nl": "Hier zijn {socket:type1_combo} stekkers van het type
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "freeform": { - "key": "socket:type1_combo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=0" - ] - } - }, - { - "id": "plugs-6", - "question": { - "en": "How much plugs of type
Tesla Supercharger
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_supercharger} plugs of type
Tesla Supercharger
available here", - "nl": "Hier zijn {socket:tesla_supercharger} stekkers van het type
Tesla Supercharger
" - }, - "freeform": { - "key": "socket:tesla_supercharger", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=0" - ] - } - }, - { - "id": "plugs-7", - "question": { - "en": "How much plugs of type
Type 2 (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 (mennekes)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2} plugs of type
Type 2 (mennekes)
available here", - "nl": "Hier zijn {socket:type2} stekkers van het type
Type 2 (mennekes)
" - }, - "freeform": { - "key": "socket:type2", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2~*", - "socket:type2!=0" - ] - } - }, - { - "id": "plugs-8", - "question": { - "en": "How much plugs of type
Type 2 CCS (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 CCS (mennekes)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2_combo} plugs of type
Type 2 CCS (mennekes)
available here", - "nl": "Hier zijn {socket:type2_combo} stekkers van het type
Type 2 CCS (mennekes)
" - }, - "freeform": { - "key": "socket:type2_combo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=0" - ] - } - }, - { - "id": "plugs-9", - "question": { - "en": "How much plugs of type
Type 2 with cable (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 met kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2_cable} plugs of type
Type 2 with cable (mennekes)
available here", - "nl": "Hier zijn {socket:type2_cable} stekkers van het type
Type 2 met kabel (J1772)
" - }, - "freeform": { - "key": "socket:type2_cable", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=0" - ] - } - }, - { - "id": "plugs-10", - "question": { - "en": "How much plugs of type
Tesla Supercharger CCS (a branded type2_css)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_supercharger_ccs} plugs of type
Tesla Supercharger CCS (a branded type2_css)
available here", - "nl": "Hier zijn {socket:tesla_supercharger_ccs} stekkers van het type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "freeform": { - "key": "socket:tesla_supercharger_ccs", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=0" - ] - } - }, - { - "id": "plugs-11", - "question": { - "en": "How much plugs of type
Tesla Supercharger (destination)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger (destination)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_destination} plugs of type
Tesla Supercharger (destination)
available here", - "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla Supercharger (destination)
" - }, - "freeform": { - "key": "socket:tesla_destination", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "plugs-12", - "question": { - "en": "How much plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_destination} plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
available here", - "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "freeform": { - "key": "socket:tesla_destination", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "plugs-13", - "question": { - "en": "How much plugs of type
USB to charge phones and small electronics
are available here?", - "nl": "Hoeveel stekkers van type
USB om GSMs en kleine electronica op te laden
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:USB-A} plugs of type
USB to charge phones and small electronics
available here", - "nl": "Hier zijn {socket:USB-A} stekkers van het type
USB om GSMs en kleine electronica op te laden
" - }, - "freeform": { - "key": "socket:USB-A", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=0" - ] - } - }, - { - "id": "plugs-14", - "question": { - "en": "How much plugs of type
Bosch Active Connect with 3 pins and cable
are available here?", - "nl": "Hoeveel stekkers van type
Bosch Active Connect met 3 pinnen aan een kabel
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:bosch_3pin} plugs of type
Bosch Active Connect with 3 pins and cable
available here", - "nl": "Hier zijn {socket:bosch_3pin} stekkers van het type
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "freeform": { - "key": "socket:bosch_3pin", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=0" - ] - } - }, - { - "id": "plugs-15", - "question": { - "en": "How much plugs of type
Bosch Active Connect with 5 pins and cable
are available here?", - "nl": "Hoeveel stekkers van type
Bosch Active Connect met 5 pinnen aan een kabel
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:bosch_5pin} plugs of type
Bosch Active Connect with 5 pins and cable
available here", - "nl": "Hier zijn {socket:bosch_5pin} stekkers van het type
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "freeform": { - "key": "socket:bosch_5pin", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=0" - ] - } - }, - { - "id": "OH", - "render": "{opening_hours_table(opening_hours)}", - "freeform": { - "key": "opening_hours", - "type": "opening_hours" - }, - "question": { - "en": "When is this charging station opened?", - "nl": "Wanneer is dit oplaadpunt beschikbaar??" - }, - "mappings": [ - { - "if": "opening_hours=24/7", - "then": { - "en": "24/7 opened (including holidays)", - "nl": "24/7 open - ook tijdens vakanties" - } - } - ] - }, - { - "id": "fee", - "question": { - "en": "Does one have to pay to use this charging station?", - "nl": "Moet men betalen om dit oplaadpunt te gebruiken?" - }, - "mappings": [ - { - "if": { - "and": [ - "fee=no" - ] - }, - "then": { - "nl": "Gratis te gebruiken", - "en": "Free to use" - }, - "hideInAnswer": true - }, - { - "if": { - "and": [ - "fee=no", - "fee:conditional=", - "charge=", - "authentication:none=yes" - ] - }, - "then": { - "nl": "Gratis te gebruiken (zonder aan te melden)", - "en": "Free to use (without authenticating)" - } - }, - { - "if": { - "and": [ - "fee=no", - "fee:conditional=", - "charge=", - "authentication:none=no" - ] - }, - "then": { - "nl": "Gratis te gebruiken, maar aanmelden met een applicatie is verplicht", - "en": "Free to use, but one has to authenticate" - } - }, - { - "if": { - "and": [ - "fee=yes", - "fee:conditional=no @ customers" - ] - }, - "then": { - "nl": "Betalend te gebruiken, maar gratis voor klanten van het bijhorende hotel/café/ziekenhuis/...", - "en": "Paid use, but free for customers of the hotel/pub/hospital/... who operates the charging station" - } - }, - { - "if": { - "and": [ - "fee=yes", - "fee:conditional=" - ] - }, - "then": { - "nl": "Betalend", - "en": "Paid use" - } - } - ] - }, - { - "id": "charge", - "question": { - "en": "How much does one have to pay to use this charging station?", - "nl": "Hoeveel moet men betalen om dit oplaadpunt te gebruiken?" - }, - "render": { - "en": "Using this charging station costs {charge}", - "nl": "Dit oplaadpunt gebruiken kost {charge}" - }, - "freeform": { - "key": "charge" - }, - "condition": "fee=yes" - }, - { - "id": "payment-options", - "builtin": "payment-options", - "override": { - "condition": { - "or": [ - "fee=yes", - "charge~*" - ] - }, - "mappings+": [ - { - "if": "payment:app=yes", - "ifnot": "payment:app=no", - "then": { - "en": "Payment is done using a dedicated app", - "nl": "Betalen via een app van het netwerk" + "id": "capacity", + "render": { + "en": "{capacity} vehicles can be charged here at the same time", + "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden", + "de": "{capacity} Fahrzeuge können hier gleichzeitig geladen werden" + }, + "question": { + "en": "How much vehicles can be charged here at the same time?", + "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?", + "de": "Wie viele Fahrzeuge können hier gleichzeitig geladen werden?" + }, + "freeform": { + "key": "capacity", + "type": "pnat" } - }, - { - "if": "payment:membership_card=yes", - "ifnot": "payment:membership_card=no", - "then": { - "en": "Payment is done using a membership card", - "nl": "Betalen via een lidkaart van het netwerk" + }, + { + "id": "Available_charging_stations (generated)", + "question": { + "en": "Which charging connections are available here?", + "nl": "Welke aansluitingen zijn hier beschikbaar?", + "de": "Welche Ladestationen gibt es hier?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "socket:schuko=1", + "ifnot": "socket:schuko=", + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "hideInAnswer": { + "or": [ + "_country!=be", + "_country!=fr", + "_country!=ma", + "_country!=tn", + "_country!=pl", + "_country!=cs", + "_country!=sk", + "_country!=mo" + ] + } + }, + { + "if": { + "and": [ + "socket:schuko~*", + "socket:schuko!=1" + ] + }, + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:typee=1", + "ifnot": "socket:typee=", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" + } + }, + { + "if": { + "and": [ + "socket:typee~*", + "socket:typee!=1" + ] + }, + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:chademo=1", + "ifnot": "socket:chademo=", + "then": { + "en": "
Chademo
", + "nl": "
Chademo
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:chademo~*", + "socket:chademo!=1" + ] + }, + "then": { + "en": "
Chademo
", + "nl": "
Chademo
", + "de": "
Chademo
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1_cable=1", + "ifnot": "socket:type1_cable=", + "then": { + "en": "
Type 1 with cable (J1772)
", + "nl": "
Type 1 met kabel (J1772)
", + "de": "
Typ 1 mit Kabel (J1772)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=1" + ] + }, + "then": { + "en": "
Type 1 with cable (J1772)
", + "nl": "
Type 1 met kabel (J1772)
", + "de": "
Typ 1 mit Kabel (J1772)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1=1", + "ifnot": "socket:type1=", + "then": { + "en": "
Type 1 without cable (J1772)
", + "nl": "
Type 1 zonder kabel (J1772)
", + "de": "
Typ 1 ohne Kabel (J1772)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1~*", + "socket:type1!=1" + ] + }, + "then": { + "en": "
Type 1 without cable (J1772)
", + "nl": "
Type 1 zonder kabel (J1772)
", + "de": "
Typ 1 ohne Kabel (J1772)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1_combo=1", + "ifnot": "socket:type1_combo=", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
", + "de": "
Typ 1 CCS (auch bekannt als Typ 1 Combo)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=1" + ] + }, + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
", + "de": "
Typ 1 CCS (auch bekannt als Typ 1 Combo)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_supercharger=1", + "ifnot": "socket:tesla_supercharger=", + "then": { + "en": "
Tesla Supercharger
", + "nl": "
Tesla Supercharger
", + "de": "
Tesla Supercharger
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger
", + "nl": "
Tesla Supercharger
", + "de": "
Tesla Supercharger
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2=1", + "ifnot": "socket:type2=", + "then": { + "en": "
Type 2 (mennekes)
", + "nl": "
Type 2 (mennekes)
", + "de": "
Typ 2 (Mennekes)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2~*", + "socket:type2!=1" + ] + }, + "then": { + "en": "
Type 2 (mennekes)
", + "nl": "
Type 2 (mennekes)
", + "de": "
Typ 2 (Mennekes)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2_combo=1", + "ifnot": "socket:type2_combo=", + "then": { + "en": "
Type 2 CCS (mennekes)
", + "nl": "
Type 2 CCS (mennekes)
", + "de": "
Typ 2 CCS (Mennekes)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=1" + ] + }, + "then": { + "en": "
Type 2 CCS (mennekes)
", + "nl": "
Type 2 CCS (mennekes)
", + "de": "
Typ 2 CCS (Mennekes)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2_cable=1", + "ifnot": "socket:type2_cable=", + "then": { + "en": "
Type 2 with cable (mennekes)
", + "nl": "
Type 2 met kabel (J1772)
", + "de": "
Typ 2 mit Kabel (Mennekes)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=1" + ] + }, + "then": { + "en": "
Type 2 with cable (mennekes)
", + "nl": "
Type 2 met kabel (J1772)
", + "de": "
Typ 2 mit Kabel (Mennekes)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_supercharger_ccs=1", + "ifnot": "socket:tesla_supercharger_ccs=", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
", + "de": "
Tesla Supercharger CCS (Typ 2 CSS)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
", + "de": "
Tesla Supercharger CCS (Typ 2 CSS)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_destination=1", + "ifnot": "socket:tesla_destination=", + "then": { + "en": "
Tesla Supercharger (destination)
", + "nl": "
Tesla Supercharger (destination)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + }, + { + "or": [ + "_country!=us" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger (destination)
", + "nl": "
Tesla Supercharger (destination)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_destination=1", + "ifnot": "socket:tesla_destination=", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + }, + { + "or": [ + "_country=us" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=1" + ] + }, + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:USB-A=1", + "ifnot": "socket:USB-A=", + "then": { + "en": "
USB to charge phones and small electronics
", + "nl": "
USB om GSMs en kleine electronica op te laden
", + "de": "
USB zum Laden von Smartphones oder Elektrokleingeräten
" + } + }, + { + "if": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=1" + ] + }, + "then": { + "en": "
USB to charge phones and small electronics
", + "nl": "
USB om GSMs en kleine electronica op te laden
", + "de": "
USB zum Laden von Smartphones und Elektrokleingeräten
" + }, + "hideInAnswer": true + }, + { + "if": "socket:bosch_3pin=1", + "ifnot": "socket:bosch_3pin=", + "then": { + "en": "
Bosch Active Connect with 3 pins and cable
", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "bicycle=no" + ] + }, + { + "and": [ + { + "or": [ + "car=yes", + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] + }, + "bicycle!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=1" + ] + }, + "then": { + "en": "
Bosch Active Connect with 3 pins and cable
", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "hideInAnswer": true + }, + { + "if": "socket:bosch_5pin=1", + "ifnot": "socket:bosch_5pin=", + "then": { + "en": "
Bosch Active Connect with 5 pins and cable
", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
", + "de": "
Bosch Active Connect mit 5 Pins und Kabel
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "bicycle=no" + ] + }, + { + "and": [ + { + "or": [ + "car=yes", + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] + }, + "bicycle!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=1" + ] + }, + "then": { + "en": "
Bosch Active Connect with 5 pins and cable
", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
", + "de": "
Bosch Active Connect mit 5 Pins und Kabel
" + }, + "hideInAnswer": true + } + ] + }, + { + "id": "plugs-0", + "question": { + "en": "How much plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
are available here?", + "nl": "Hoeveel stekkers van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:schuko} plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
available here", + "nl": "Hier zijn {socket:schuko} stekkers van het type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "freeform": { + "key": "socket:schuko", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:schuko~*", + "socket:schuko!=0" + ] } - } - ] - } - }, - { - "id": "Authentication", - "#": "In some cases, charging is free but one has to be authenticated. We only ask for authentication if fee is no (or unset). By default one sees the questions for either the payment options or the authentication options, but normally not both", - "question": { - "en": "What kind of authentication is available at the charging station?", - "nl": "Hoe kan men zich aanmelden aan dit oplaadstation?" - }, - "multiAnswer": true, - "mappings": [ - { - "if": "authentication:membership_card=yes", - "ifnot": "authentication:membership_card=no", - "then": { - "en": "Authentication by a membership card", - "nl": "Aanmelden met een lidkaart is mogelijk" - } }, { - "if": "authentication:app=yes", - "ifnot": "authentication:app=no", - "then": { - "en": "Authentication by an app", - "nl": "Aanmelden via een applicatie is mogelijk" - } + "id": "plugs-1", + "question": { + "en": "How much plugs of type
European wall plug with ground pin (CEE7/4 type E)
are available here?", + "nl": "Hoeveel stekkers van type
Europese stekker met aardingspin (CEE7/4 type E)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:typee} plugs of type
European wall plug with ground pin (CEE7/4 type E)
available here", + "nl": "Hier zijn {socket:typee} stekkers van het type
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "freeform": { + "key": "socket:typee", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:typee~*", + "socket:typee!=0" + ] + } }, { - "if": "authentication:phone_call=yes", - "ifnot": "authentication:phone_call=no", - "then": { - "en": "Authentication via phone call is available", - "nl": "Aanmelden door te bellen naar een telefoonnummer is mogelijk" - } + "id": "plugs-2", + "question": { + "en": "How much plugs of type
Chademo
are available here?", + "nl": "Hoeveel stekkers van type
Chademo
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:chademo} plugs of type
Chademo
available here", + "nl": "Hier zijn {socket:chademo} stekkers van het type
Chademo
" + }, + "freeform": { + "key": "socket:chademo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:chademo~*", + "socket:chademo!=0" + ] + } }, { - "if": "authentication:short_message=yes", - "ifnot": "authentication:short_message=no", - "then": { - "en": "Authentication via SMS is available", - "nl": "Aanmelden via SMS is mogelijk" - } + "id": "plugs-3", + "question": { + "en": "How much plugs of type
Type 1 with cable (J1772)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 met kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1_cable} plugs of type
Type 1 with cable (J1772)
available here", + "nl": "Hier zijn {socket:type1_cable} stekkers van het type
Type 1 met kabel (J1772)
" + }, + "freeform": { + "key": "socket:type1_cable", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=0" + ] + } }, { - "if": "authentication:nfc=yes", - "ifnot": "authentication:nfc=no", - "then": { - "en": "Authentication via NFC is available", - "nl": "Aanmelden via NFC is mogelijk" - } + "id": "plugs-4", + "question": { + "en": "How much plugs of type
Type 1 without cable (J1772)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 zonder kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1} plugs of type
Type 1 without cable (J1772)
available here", + "nl": "Hier zijn {socket:type1} stekkers van het type
Type 1 zonder kabel (J1772)
" + }, + "freeform": { + "key": "socket:type1", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1~*", + "socket:type1!=0" + ] + } }, { - "if": "authentication:money_card=yes", - "ifnot": "authentication:money_card=no", - "then": { - "en": "Authentication via Money Card is available", - "nl": "Aanmelden met Money Card is mogelijk" - } + "id": "plugs-5", + "question": { + "en": "How much plugs of type
Type 1 CCS (aka Type 1 Combo)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 CCS (ook gekend als Type 1 Combo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1_combo} plugs of type
Type 1 CCS (aka Type 1 Combo)
available here", + "nl": "Hier zijn {socket:type1_combo} stekkers van het type
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "freeform": { + "key": "socket:type1_combo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=0" + ] + } }, { - "if": "authentication:debit_card=yes", - "ifnot": "authentication:debit_card=no", - "then": { - "en": "Authentication via debit card is available", - "nl": "Aanmelden met een betaalkaart is mogelijk" - } + "id": "plugs-6", + "question": { + "en": "How much plugs of type
Tesla Supercharger
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_supercharger} plugs of type
Tesla Supercharger
available here", + "nl": "Hier zijn {socket:tesla_supercharger} stekkers van het type
Tesla Supercharger
" + }, + "freeform": { + "key": "socket:tesla_supercharger", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=0" + ] + } }, { - "if": "authentication:none=yes", - "ifnot": "authentication:none=no", - "then": { - "en": "Charging here is (also) possible without authentication", - "nl": "Hier opladen is (ook) mogelijk zonder aan te melden" - } - } - ], - "condition": { - "or": [ - "fee=no", - "fee=" - ] - } - }, - { - "id": "Auth phone", - "render": { - "en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}", - "nl": "Aanmelden door te bellen of te SMS'en naar {authentication:phone_call:number}" - }, - "question": { - "en": "What's the phone number for authentication call or SMS?", - "nl": "Wat is het telefoonnummer dat men moet bellen of SMS'en om zich aan te melden?" - }, - "freeform": { - "key": "authentication:phone_call:number", - "type": "phone" - }, - "condition": { - "or": [ - "authentication:phone_call=yes", - "authentication:short_message=yes" - ] - } - }, - { - "id": "maxstay", - "question": { - "en": "What is the maximum amount of time one is allowed to stay here?", - "nl": "Hoelang mag een voertuig hier blijven staan?" - }, - "freeform": { - "key": "maxstay" - }, - "render": { - "en": "One can stay at most {canonical(maxstay)}", - "nl": "De maximale parkeertijd hier is {canonical(maxstay)}" - }, - "mappings": [ - { - "if": "maxstay=unlimited", - "then": { - "en": "No timelimit on leaving your vehicle here", - "nl": "Geen maximum parkeertijd" - } - } - ], - "condition": { - "or": [ - "maxstay~*", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] - } - }, - { - "id": "Network", - "render": { - "en": "Part of the network {network}", - "nl": "Maakt deel uit van het {network}-netwerk" - }, - "question": { - "en": "Is this charging station part of a network?", - "nl": "Is dit oplaadpunt deel van een groter netwerk?" - }, - "freeform": { - "key": "network" - }, - "mappings": [ - { - "if": "no:network=yes", - "then": { - "en": "Not part of a bigger network", - "nl": "Maakt geen deel uit van een groter netwerk" - } + "id": "plugs-7", + "question": { + "en": "How much plugs of type
Type 2 (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 (mennekes)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2} plugs of type
Type 2 (mennekes)
available here", + "nl": "Hier zijn {socket:type2} stekkers van het type
Type 2 (mennekes)
" + }, + "freeform": { + "key": "socket:type2", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2~*", + "socket:type2!=0" + ] + } }, { - "if": "network=none", - "then": { - "en": "Not part of a bigger network", - "nl": "Maakt geen deel uit van een groter netwerk" - }, - "hideInAnswer": true + "id": "plugs-8", + "question": { + "en": "How much plugs of type
Type 2 CCS (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 CCS (mennekes)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2_combo} plugs of type
Type 2 CCS (mennekes)
available here", + "nl": "Hier zijn {socket:type2_combo} stekkers van het type
Type 2 CCS (mennekes)
" + }, + "freeform": { + "key": "socket:type2_combo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=0" + ] + } }, { - "if": "network=AeroVironment", - "then": "AeroVironment" + "id": "plugs-9", + "question": { + "en": "How much plugs of type
Type 2 with cable (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 met kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2_cable} plugs of type
Type 2 with cable (mennekes)
available here", + "nl": "Hier zijn {socket:type2_cable} stekkers van het type
Type 2 met kabel (J1772)
" + }, + "freeform": { + "key": "socket:type2_cable", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=0" + ] + } }, { - "if": "network=Blink", - "then": "Blink" + "id": "plugs-10", + "question": { + "en": "How much plugs of type
Tesla Supercharger CCS (a branded type2_css)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_supercharger_ccs} plugs of type
Tesla Supercharger CCS (a branded type2_css)
available here", + "nl": "Hier zijn {socket:tesla_supercharger_ccs} stekkers van het type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "freeform": { + "key": "socket:tesla_supercharger_ccs", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=0" + ] + } }, { - "if": "network=eVgo", - "then": "eVgo" - } - ] - }, - { - "id": "Operator", - "question": { - "en": "Who is the operator of this charging station?", - "nl": "Wie beheert dit oplaadpunt?" - }, - "render": { - "en": "This charging station is operated by {operator}", - "nl": "Wordt beheerd door {operator}" - }, - "freeform": { - "key": "operator" - }, - "mappings": [ + "id": "plugs-11", + "question": { + "en": "How much plugs of type
Tesla Supercharger (destination)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger (destination)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_destination} plugs of type
Tesla Supercharger (destination)
available here", + "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla Supercharger (destination)
" + }, + "freeform": { + "key": "socket:tesla_destination", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, { - "if": { - "and": [ - "network:={operator}" + "id": "plugs-12", + "question": { + "en": "How much plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_destination} plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
available here", + "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "freeform": { + "key": "socket:tesla_destination", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "plugs-13", + "question": { + "en": "How much plugs of type
USB to charge phones and small electronics
are available here?", + "nl": "Hoeveel stekkers van type
USB om GSMs en kleine electronica op te laden
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:USB-A} plugs of type
USB to charge phones and small electronics
available here", + "nl": "Hier zijn {socket:USB-A} stekkers van het type
USB om GSMs en kleine electronica op te laden
" + }, + "freeform": { + "key": "socket:USB-A", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=0" + ] + } + }, + { + "id": "plugs-14", + "question": { + "en": "How much plugs of type
Bosch Active Connect with 3 pins and cable
are available here?", + "nl": "Hoeveel stekkers van type
Bosch Active Connect met 3 pinnen aan een kabel
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:bosch_3pin} plugs of type
Bosch Active Connect with 3 pins and cable
available here", + "nl": "Hier zijn {socket:bosch_3pin} stekkers van het type
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "freeform": { + "key": "socket:bosch_3pin", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=0" + ] + } + }, + { + "id": "plugs-15", + "question": { + "en": "How much plugs of type
Bosch Active Connect with 5 pins and cable
are available here?", + "nl": "Hoeveel stekkers van type
Bosch Active Connect met 5 pinnen aan een kabel
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:bosch_5pin} plugs of type
Bosch Active Connect with 5 pins and cable
available here", + "nl": "Hier zijn {socket:bosch_5pin} stekkers van het type
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "freeform": { + "key": "socket:bosch_5pin", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=0" + ] + } + }, + { + "id": "OH", + "render": "{opening_hours_table(opening_hours)}", + "freeform": { + "key": "opening_hours", + "type": "opening_hours" + }, + "question": { + "en": "When is this charging station opened?", + "nl": "Wanneer is dit oplaadpunt beschikbaar??", + "de": "Wann ist diese Ladestation geöffnet?", + "it": "Quali sono gli orari di apertura di questa stazione di ricarica?", + "ja": "この充電ステーションはいつオープンしますか?", + "nb_NO": "Når åpnet denne ladestasjonen?", + "ru": "В какое время работает эта зарядная станция?", + "zh_Hant": "何時是充電站開放使用的時間?" + }, + "mappings": [ + { + "if": "opening_hours=24/7", + "then": { + "en": "24/7 opened (including holidays)", + "nl": "24/7 open - ook tijdens vakanties", + "de": "durchgehend geöffnet (auch an Feiertagen)" + } + } ] - }, - "then": { - "en": "Actually, {operator} is the network", - "nl": "Eigenlijk is {operator} het netwerk waarvan het deel uitmaakt" - }, - "addExtraTags": [ - "operator=" - ], - "hideInAnswer": "operator=" + }, + { + "id": "fee", + "question": { + "en": "Does one have to pay to use this charging station?", + "nl": "Moet men betalen om dit oplaadpunt te gebruiken?" + }, + "mappings": [ + { + "if": { + "and": [ + "fee=no" + ] + }, + "then": { + "nl": "Gratis te gebruiken", + "en": "Free to use" + }, + "hideInAnswer": true + }, + { + "if": { + "and": [ + "fee=no", + "fee:conditional=", + "charge=", + "authentication:none=yes" + ] + }, + "then": { + "nl": "Gratis te gebruiken (zonder aan te melden)", + "en": "Free to use (without authenticating)" + } + }, + { + "if": { + "and": [ + "fee=no", + "fee:conditional=", + "charge=", + "authentication:none=no" + ] + }, + "then": { + "nl": "Gratis te gebruiken, maar aanmelden met een applicatie is verplicht", + "en": "Free to use, but one has to authenticate" + } + }, + { + "if": { + "and": [ + "fee=yes", + "fee:conditional=no @ customers" + ] + }, + "then": { + "nl": "Betalend te gebruiken, maar gratis voor klanten van het bijhorende hotel/café/ziekenhuis/...", + "en": "Paid use, but free for customers of the hotel/pub/hospital/... who operates the charging station" + } + }, + { + "if": { + "and": [ + "fee=yes", + "fee:conditional=" + ] + }, + "then": { + "nl": "Betalend", + "en": "Paid use" + } + } + ] + }, + { + "id": "charge", + "question": { + "en": "How much does one have to pay to use this charging station?", + "nl": "Hoeveel moet men betalen om dit oplaadpunt te gebruiken?" + }, + "render": { + "en": "Using this charging station costs {charge}", + "nl": "Dit oplaadpunt gebruiken kost {charge}" + }, + "freeform": { + "key": "charge" + }, + "condition": "fee=yes" + }, + { + "id": "payment-options", + "builtin": "payment-options", + "override": { + "condition": { + "or": [ + "fee=yes", + "charge~*" + ] + }, + "mappings+": [ + { + "if": "payment:app=yes", + "ifnot": "payment:app=no", + "then": { + "en": "Payment is done using a dedicated app", + "nl": "Betalen via een app van het netwerk", + "de": "Bezahlung mit einer speziellen App" + } + }, + { + "if": "payment:membership_card=yes", + "ifnot": "payment:membership_card=no", + "then": { + "en": "Payment is done using a membership card", + "nl": "Betalen via een lidkaart van het netwerk", + "de": "Bezahlung mit einer Mitgliedskarte" + } + } + ] + } + }, + { + "id": "Authentication", + "#": "In some cases, charging is free but one has to be authenticated. We only ask for authentication if fee is no (or unset). By default one sees the questions for either the payment options or the authentication options, but normally not both", + "question": { + "en": "What kind of authentication is available at the charging station?", + "nl": "Hoe kan men zich aanmelden aan dit oplaadstation?", + "de": "Welche Authentifizierung ist an der Ladestation möglich?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "authentication:membership_card=yes", + "ifnot": "authentication:membership_card=no", + "then": { + "en": "Authentication by a membership card", + "nl": "Aanmelden met een lidkaart is mogelijk", + "de": "Authentifizierung durch eine Mitgliedskarte" + } + }, + { + "if": "authentication:app=yes", + "ifnot": "authentication:app=no", + "then": { + "en": "Authentication by an app", + "nl": "Aanmelden via een applicatie is mogelijk", + "de": "Authentifizierung durch eine App" + } + }, + { + "if": "authentication:phone_call=yes", + "ifnot": "authentication:phone_call=no", + "then": { + "en": "Authentication via phone call is available", + "nl": "Aanmelden door te bellen naar een telefoonnummer is mogelijk", + "de": "Authentifizierung per Anruf ist möglich" + } + }, + { + "if": "authentication:short_message=yes", + "ifnot": "authentication:short_message=no", + "then": { + "en": "Authentication via SMS is available", + "nl": "Aanmelden via SMS is mogelijk", + "de": "Authentifizierung per Anruf ist möglich" + } + }, + { + "if": "authentication:nfc=yes", + "ifnot": "authentication:nfc=no", + "then": { + "en": "Authentication via NFC is available", + "nl": "Aanmelden via NFC is mogelijk", + "de": "Authentifizierung über NFC ist möglich" + } + }, + { + "if": "authentication:money_card=yes", + "ifnot": "authentication:money_card=no", + "then": { + "en": "Authentication via Money Card is available", + "nl": "Aanmelden met Money Card is mogelijk", + "de": "Authentifizierung über Geldkarte ist möglich" + } + }, + { + "if": "authentication:debit_card=yes", + "ifnot": "authentication:debit_card=no", + "then": { + "en": "Authentication via debit card is available", + "nl": "Aanmelden met een betaalkaart is mogelijk", + "de": "Authentifizierung per Debitkarte ist möglich" + } + }, + { + "if": "authentication:none=yes", + "ifnot": "authentication:none=no", + "then": { + "en": "Charging here is (also) possible without authentication", + "nl": "Hier opladen is (ook) mogelijk zonder aan te melden", + "de": "Das Aufladen ist hier (auch) ohne Authentifizierung möglich" + } + } + ], + "condition": { + "or": [ + "fee=no", + "fee=" + ] + } + }, + { + "id": "Auth phone", + "render": { + "en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}", + "nl": "Aanmelden door te bellen of te SMS'en naar {authentication:phone_call:number}", + "de": "Authentifizierung durch Anruf oder SMS an {authentication:phone_call:number}" + }, + "question": { + "en": "What's the phone number for authentication call or SMS?", + "nl": "Wat is het telefoonnummer dat men moet bellen of SMS'en om zich aan te melden?", + "de": "Wie lautet die Telefonnummer für den Authentifizierungsanruf oder die SMS?" + }, + "freeform": { + "key": "authentication:phone_call:number", + "type": "phone" + }, + "condition": { + "or": [ + "authentication:phone_call=yes", + "authentication:short_message=yes" + ] + } + }, + { + "id": "maxstay", + "question": { + "en": "What is the maximum amount of time one is allowed to stay here?", + "nl": "Hoelang mag een voertuig hier blijven staan?", + "de": "Was ist die Höchstdauer des Aufenthalts hier?" + }, + "freeform": { + "key": "maxstay" + }, + "render": { + "en": "One can stay at most {canonical(maxstay)}", + "nl": "De maximale parkeertijd hier is {canonical(maxstay)}", + "de": "Die maximale Parkzeit beträgt {canonical(maxstay)}" + }, + "mappings": [ + { + "if": "maxstay=unlimited", + "then": { + "en": "No timelimit on leaving your vehicle here", + "nl": "Geen maximum parkeertijd", + "de": "Keine Höchstparkdauer" + } + } + ], + "condition": { + "or": [ + "maxstay~*", + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] + } + }, + { + "id": "Network", + "render": { + "en": "Part of the network {network}", + "nl": "Maakt deel uit van het {network}-netwerk", + "de": "Teil des Netzwerks {network}", + "it": "{network}", + "ja": "{network}", + "nb_NO": "{network}", + "ru": "{network}", + "zh_Hant": "{network}" + }, + "question": { + "en": "Is this charging station part of a network?", + "nl": "Is dit oplaadpunt deel van een groter netwerk?", + "de": "Ist diese Ladestation Teil eines Netzwerks?", + "it": "A quale rete appartiene questa stazione di ricarica?", + "ja": "この充電ステーションの運営チェーンはどこですか?", + "ru": "К какой сети относится эта станция?", + "zh_Hant": "充電站所屬的網路是?" + }, + "freeform": { + "key": "network" + }, + "mappings": [ + { + "if": "no:network=yes", + "then": { + "en": "Not part of a bigger network", + "nl": "Maakt geen deel uit van een groter netwerk", + "de": "Nicht Teil eines größeren Netzwerks" + } + }, + { + "if": "network=none", + "then": { + "en": "Not part of a bigger network", + "nl": "Maakt geen deel uit van een groter netwerk", + "de": "Nicht Teil eines größeren Netzwerks" + }, + "hideInAnswer": true + }, + { + "if": "network=AeroVironment", + "then": "AeroVironment" + }, + { + "if": "network=Blink", + "then": "Blink" + }, + { + "if": "network=eVgo", + "then": "eVgo" + } + ] + }, + { + "id": "Operator", + "question": { + "en": "Who is the operator of this charging station?", + "nl": "Wie beheert dit oplaadpunt?", + "de": "Wer ist der Betreiber dieser Ladestation?" + }, + "render": { + "en": "This charging station is operated by {operator}", + "nl": "Wordt beheerd door {operator}", + "de": "Diese Ladestation wird betrieben von {operator}" + }, + "freeform": { + "key": "operator" + }, + "mappings": [ + { + "if": { + "and": [ + "network:={operator}" + ] + }, + "then": { + "en": "Actually, {operator} is the network", + "nl": "Eigenlijk is {operator} het netwerk waarvan het deel uitmaakt", + "de": "Eigentlich ist {operator} das Netzwerk" + }, + "hideInAnswer": "operator=" + } + ] + }, + { + "id": "phone", + "question": { + "en": "What number can one call if there is a problem with this charging station?", + "nl": "Wat is het telefoonnummer van de beheerder van dit oplaadpunt?", + "de": "Welche Nummer kann man anrufen, wenn es ein Problem mit dieser Ladestation gibt?" + }, + "render": { + "en": "In case of problems, call {phone}", + "nl": "Bij problemen, bel naar {phone}", + "de": "Bei Problemen, anrufen unter {phone}" + }, + "freeform": { + "key": "phone", + "type": "phone" + } + }, + { + "id": "email", + "question": { + "en": "What is the email address of the operator?", + "nl": "Wat is het email-adres van de operator?", + "de": "Wie ist die Email-Adresse des Betreibers?" + }, + "render": { + "en": "In case of problems, send an email to {email}", + "nl": "Bij problemen, email naar {email}", + "de": "Bei Problemen senden Sie eine E-Mail an {email}" + }, + "freeform": { + "key": "email", + "type": "email" + } + }, + { + "id": "website", + "question": { + "en": "What is the website where one can find more information about this charging station?", + "nl": "Wat is de website waar men meer info kan vinden over dit oplaadpunt?", + "de": "Wie ist die Webseite des Betreibers?" + }, + "render": { + "en": "More info on {website}", + "nl": "Meer informatie op {website}", + "de": "Weitere Informationen auf {website}" + }, + "freeform": { + "key": "website", + "type": "url" + } + }, + "level", + { + "id": "ref", + "question": { + "en": "What is the reference number of this charging station?", + "nl": "Wat is het referentienummer van dit oplaadstation?", + "de": "Wie lautet die Kennung dieser Ladestation?" + }, + "render": { + "en": "Reference number is {ref}", + "nl": "Het referentienummer van dit oplaadpunt is {ref}", + "de": "Die Kennziffer ist {ref}" + }, + "freeform": { + "key": "ref" + }, + "#": "Only asked if part of a bigger network. Small operators typically don't have a reference number", + "condition": "network!=" + }, + { + "id": "Operational status", + "question": { + "en": "Is this charging point in use?", + "nl": "Is dit oplaadpunt operationeel?", + "de": "Ist dieser Ladepunkt in Betrieb?" + }, + "mappings": [ + { + "if": { + "and": [ + "planned:amenity=", + "construction:amenity=", + "disused:amenity=", + "operational_status=", + "amenity=charging_station" + ] + }, + "then": { + "en": "This charging station works", + "nl": "Dit oplaadpunt werkt", + "de": "Diese Ladestation funktioniert" + } + }, + { + "if": { + "and": [ + "planned:amenity=", + "construction:amenity=", + "disused:amenity=", + "operational_status=broken", + "amenity=charging_station" + ] + }, + "then": { + "en": "This charging station is broken", + "nl": "Dit oplaadpunt is kapot", + "de": "Diese Ladestation ist kaputt" + } + }, + { + "if": { + "and": [ + "planned:amenity=charging_station", + "construction:amenity=", + "disused:amenity=", + "operational_status=", + "amenity=" + ] + }, + "then": { + "en": "A charging station is planned here", + "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden", + "de": "Hier ist eine Ladestation geplant" + } + }, + { + "if": { + "and": [ + "planned:amenity=", + "construction:amenity=charging_station", + "disused:amenity=", + "operational_status=", + "amenity=" + ] + }, + "then": { + "en": "A charging station is constructed here", + "nl": "Hier wordt op dit moment een oplaadpunt gebouwd", + "de": "Hier wird eine Ladestation gebaut" + } + }, + { + "if": { + "and": [ + "planned:amenity=", + "construction:amenity=", + "disused:amenity=charging_station", + "operational_status=", + "amenity=" + ] + }, + "then": { + "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", + "de": "Diese Ladestation wurde dauerhaft deaktiviert und wird nicht mehr benutzt, ist aber noch sichtbar" + } + } + ] + }, + { + "id": "Parking:fee", + "question": { + "en": "Does one have to pay a parking fee while charging?", + "nl": "Moet men parkeergeld betalen tijdens het opladen?", + "de": "Muss man beim Laden eine Parkgebühr bezahlen?" + }, + "mappings": [ + { + "if": "parking:fee=no", + "then": { + "en": "No additional parking cost while charging", + "nl": "Geen extra parkeerkost tijdens het opladen", + "de": "Keine zusätzlichen Parkgebühren beim Laden" + } + }, + { + "if": "parking:fee=yes", + "then": { + "en": "An additional parking fee should be paid while charging", + "nl": "Tijdens het opladen moet er parkeergeld betaald worden", + "de": "Beim Laden ist eine zusätzliche Parkgebühr zu entrichten" + } + } + ], + "condition": { + "or": [ + "motor_vehicle=yes", + "hgv=yes", + "bus=yes", + "bicycle=no", + "bicycle=" + ] + } } - ] - }, - { - "id": "phone", - "question": { - "en": "What number can one call if there is a problem with this charging station?", - "nl": "Wat is het telefoonnummer van de beheerder van dit oplaadpunt?" - }, - "render": { - "en": "In case of problems, call {phone}", - "nl": "Bij problemen, bel naar {phone}" - }, - "freeform": { - "key": "phone", - "type": "phone" - } - }, - { - "id": "email", - "question": { - "en": "What is the email address of the operator?", - "nl": "Wat is het email-adres van de operator?" - }, - "render": { - "en": "In case of problems, send an email to {email}", - "nl": "Bij problemen, email naar {email}" - }, - "freeform": { - "key": "email", - "type": "email" - } - }, - { - "id": "website", - "question": { - "en": "What is the website where one can find more information about this charging station?", - "nl": "Wat is de website waar men meer info kan vinden over dit oplaadpunt?" - }, - "render": { - "en": "More info on {website}", - "nl": "Meer informatie op {website}" - }, - "freeform": { - "key": "website", - "type": "url" - } - }, - "level", - { - "id": "ref", - "question": { - "en": "What is the reference number of this charging station?", - "nl": "Wat is het referentienummer van dit oplaadstation?" - }, - "render": { - "en": "Reference number is {ref}", - "nl": "Het referentienummer van dit oplaadpunt is {ref}" - }, - "freeform": { - "key": "ref" - }, - "#": "Only asked if part of a bigger network. Small operators typically don't have a reference number", - "condition": "network!=" - }, - { - "id": "Operational status", - "question": { - "en": "Is this charging point in use?", - "nl": "Is dit oplaadpunt operationeel?" - }, - "mappings": [ + ], + "presets": [ { - "if": { - "and": [ - "planned:amenity=", - "construction:amenity=", - "disused:amenity=", - "operational_status=", - "amenity=charging_station" - ] - }, - "then": { - "en": "This charging station works", - "nl": "Dit oplaadpunt werkt" - } + "tags": [ + "amenity=charging_station", + "motorcar=no", + "bicycle=yes", + "socket:typee=1" + ], + "title": { + "en": "charging station with a normal european wall plug (meant to charge electrical bikes)", + "nl": "laadpunt met gewone stekker(s) (bedoeld om electrische fietsen op te laden)", + "de": "Ladestation", + "ru": "Зарядная станция" + }, + "preciseInput": { + "preferredBackground": "map" + } }, { - "if": { - "and": [ - "planned:amenity=", - "construction:amenity=", - "disused:amenity=", - "operational_status=broken", - "amenity=charging_station" - ] - }, - "then": { - "en": "This charging station is broken", - "nl": "Dit oplaadpunt is kapot" - } + "tags": [ + "amenity=charging_station", + "motorcar=no", + "bicycle=yes" + ], + "title": { + "en": "charging station for e-bikes", + "nl": "oplaadpunt voor elektrische fietsen" + }, + "preciseInput": { + "preferredBackground": "map" + } }, { - "if": { - "and": [ - "planned:amenity=charging_station", - "construction:amenity=", - "disused:amenity=", - "operational_status=", - "amenity=" - ] - }, - "then": { - "en": "A charging station is planned here", - "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden" - } + "tags": [ + "amenity=charging_station", + "motorcar=yes", + "bicycle=no" + ], + "title": { + "en": "charging station for cars", + "nl": "oplaadstation voor elektrische auto's" + }, + "preciseInput": { + "preferredBackground": "map" + } }, { - "if": { - "and": [ - "planned:amenity=", - "construction:amenity=charging_station", - "disused:amenity=", - "operational_status=", - "amenity=" - ] - }, - "then": { - "en": "A charging station is constructed here", - "nl": "Hier wordt op dit moment een oplaadpunt gebouwd" - } - }, - { - "if": { - "and": [ - "planned:amenity=", - "construction:amenity=", - "disused:amenity=charging_station", - "operational_status=", - "amenity=" - ] - }, - "then": { - "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" - } + "tags": [ + "amenity=charging_station" + ], + "title": { + "en": "charging station", + "nl": "oplaadstation" + }, + "preciseInput": { + "preferredBackground": "map" + } } - ] - }, - { - "id": "Parking:fee", - "question": { - "en": "Does one have to pay a parking fee while charging?", - "nl": "Moet men parkeergeld betalen tijdens het opladen?" - }, - "mappings": [ + ], + "wayHandling": 1, + "filter": [ { - "if": "parking:fee=no", - "then": { - "en": "No additional parking cost while charging", - "nl": "Geen extra parkeerkost tijdens het opladen" - } + "id": "vehicle-type", + "options": [ + { + "question": { + "en": "All vehicle types", + "nl": "Alle voertuigen", + "de": "Alle Fahrzeugtypen" + } + }, + { + "question": { + "en": "Charging station for bicycles", + "nl": "Oplaadpunten voor fietsen", + "de": "Ladestation für Fahrräder" + }, + "osmTags": "bicycle=yes" + }, + { + "question": { + "en": "Charging station for cars", + "nl": "Oplaadpunten voor auto's", + "de": "Ladestation für Autos" + }, + "osmTags": { + "or": [ + "car=yes", + "motorcar=yes" + ] + } + } + ] }, { - "if": "parking:fee=yes", - "then": { - "en": "An additional parking fee should be paid while charging", - "nl": "Tijdens het opladen moet er parkeergeld betaald worden" - } + "id": "working", + "options": [ + { + "question": { + "en": "Only working charging stations", + "nl": "Enkel werkende oplaadpunten", + "de": "Nur funktionierende Ladestationen" + }, + "osmTags": { + "and": [ + "operational_status!=broken", + "amenity=charging_station" + ] + } + } + ] + }, + { + "id": "connection_type", + "options": [ + { + "question": { + "en": "All connectors", + "nl": "Alle types", + "de": "Alle Anschlüsse" + } + }, + { + "question": { + "en": "Has a
Schuko wall plug without ground pin (CEE7/4 type F)
connector", + "nl": "Heeft een
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "osmTags": "socket:schuko~*" + }, + { + "question": { + "en": "Has a
European wall plug with ground pin (CEE7/4 type E)
connector", + "nl": "Heeft een
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "osmTags": "socket:typee~*" + }, + { + "question": { + "en": "Has a
Chademo
connector", + "nl": "Heeft een
Chademo
", + "de": "Hat einen
Chademo
Stecker" + }, + "osmTags": "socket:chademo~*" + }, + { + "question": { + "en": "Has a
Type 1 with cable (J1772)
connector", + "nl": "Heeft een
Type 1 met kabel (J1772)
" + }, + "osmTags": "socket:type1_cable~*" + }, + { + "question": { + "en": "Has a
Type 1 without cable (J1772)
connector", + "nl": "Heeft een
Type 1 zonder kabel (J1772)
" + }, + "osmTags": "socket:type1~*" + }, + { + "question": { + "en": "Has a
Type 1 CCS (aka Type 1 Combo)
connector", + "nl": "Heeft een
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "osmTags": "socket:type1_combo~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger
connector", + "nl": "Heeft een
Tesla Supercharger
", + "de": "Hat einen
Tesla Supercharger
Stecker" + }, + "osmTags": "socket:tesla_supercharger~*" + }, + { + "question": { + "en": "Has a
Type 2 (mennekes)
connector", + "nl": "Heeft een
Type 2 (mennekes)
" + }, + "osmTags": "socket:type2~*" + }, + { + "question": { + "en": "Has a
Type 2 CCS (mennekes)
connector", + "nl": "Heeft een
Type 2 CCS (mennekes)
" + }, + "osmTags": "socket:type2_combo~*" + }, + { + "question": { + "en": "Has a
Type 2 with cable (mennekes)
connector", + "nl": "Heeft een
Type 2 met kabel (J1772)
" + }, + "osmTags": "socket:type2_cable~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger CCS (a branded type2_css)
connector", + "nl": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "osmTags": "socket:tesla_supercharger_ccs~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger (destination)
connector", + "nl": "Heeft een
Tesla Supercharger (destination)
" + }, + "osmTags": "socket:tesla_destination~*" + }, + { + "question": { + "en": "Has a
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
connector", + "nl": "Heeft een
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "osmTags": "socket:tesla_destination~*" + }, + { + "question": { + "en": "Has a
USB to charge phones and small electronics
connector", + "nl": "Heeft een
USB om GSMs en kleine electronica op te laden
" + }, + "osmTags": "socket:USB-A~*" + }, + { + "question": { + "en": "Has a
Bosch Active Connect with 3 pins and cable
connector", + "nl": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "osmTags": "socket:bosch_3pin~*" + }, + { + "question": { + "en": "Has a
Bosch Active Connect with 5 pins and cable
connector", + "nl": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "osmTags": "socket:bosch_5pin~*" + } + ] } - ], - "condition": { - "or": [ - "motor_vehicle=yes", - "hgv=yes", - "bus=yes", - "bicycle=no", - "bicycle=" - ] - } - } - ], - "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" - ] + ], + "units": [ + { + "appliesToKey": [ + "maxstay" + ], + "applicableUnits": [ + { + "canonicalDenomination": "minutes", + "canonicalDenominationSingular": "minute", + "alternativeDenomination": [ + "m", + "min", + "mins", + "minuten", + "mns" + ], + "human": { + "en": " minutes", + "nl": " minuten", + "de": " Minuten", + "ru": " минут" + }, + "humanSingular": { + "en": " minute", + "nl": " minuut", + "de": " Minute", + "ru": " минута" + } + }, + { + "canonicalDenomination": "hours", + "canonicalDenominationSingular": "hour", + "alternativeDenomination": [ + "h", + "hrs", + "hours", + "u", + "uur", + "uren" + ], + "human": { + "en": " hours", + "nl": " uren", + "de": " Stunden", + "ru": " часов" + }, + "humanSingular": { + "en": " hour", + "nl": " uur", + "de": " Stunde", + "ru": " час" + } + }, + { + "canonicalDenomination": "days", + "canonicalDenominationSingular": "day", + "alternativeDenomination": [ + "dys", + "dagen", + "dag" + ], + "human": { + "en": " days", + "nl": " day", + "de": " Tage", + "ru": " дней" + }, + "humanSingular": { + "en": " day", + "nl": " dag", + "de": " Tag", + "ru": " день" + } + } + ] }, - "then": "pin:#fff;./assets/themes/charging_stations/car.svg" - } + { + "appliesToKey": [ + "socket:schuko:voltage", + "socket:typee:voltage", + "socket:chademo:voltage", + "socket:type1_cable:voltage", + "socket:type1:voltage", + "socket:type1_combo:voltage", + "socket:tesla_supercharger:voltage", + "socket:type2:voltage", + "socket:type2_combo:voltage", + "socket:type2_cable:voltage", + "socket:tesla_supercharger_ccs:voltage", + "socket:tesla_destination:voltage", + "socket:tesla_destination:voltage", + "socket:USB-A:voltage", + "socket:bosch_3pin:voltage", + "socket:bosch_5pin:voltage" + ], + "applicableUnits": [ + { + "canonicalDenomination": "V", + "alternativeDenomination": [ + "v", + "volt", + "voltage", + "V", + "Volt" + ], + "human": { + "en": "Volts", + "nl": "volt", + "de": "Volt", + "ru": "Вольт" + } + } + ], + "eraseInvalidValues": true + }, + { + "appliesToKey": [ + "socket:schuko:current", + "socket:typee:current", + "socket:chademo:current", + "socket:type1_cable:current", + "socket:type1:current", + "socket:type1_combo:current", + "socket:tesla_supercharger:current", + "socket:type2:current", + "socket:type2_combo:current", + "socket:type2_cable:current", + "socket:tesla_supercharger_ccs:current", + "socket:tesla_destination:current", + "socket:tesla_destination:current", + "socket:USB-A:current", + "socket:bosch_3pin:current", + "socket:bosch_5pin:current" + ], + "applicableUnits": [ + { + "canonicalDenomination": "A", + "alternativeDenomination": [ + "a", + "amp", + "amperage", + "A" + ], + "human": { + "en": "A", + "nl": "A" + } + } + ], + "eraseInvalidValues": true + }, + { + "appliesToKey": [ + "socket:schuko:output", + "socket:typee:output", + "socket:chademo:output", + "socket:type1_cable:output", + "socket:type1:output", + "socket:type1_combo:output", + "socket:tesla_supercharger:output", + "socket:type2:output", + "socket:type2_combo:output", + "socket:type2_cable:output", + "socket:tesla_supercharger_ccs:output", + "socket:tesla_destination:output", + "socket:tesla_destination:output", + "socket:USB-A:output", + "socket:bosch_3pin:output", + "socket:bosch_5pin:output" + ], + "applicableUnits": [ + { + "canonicalDenomination": "kW", + "alternativeDenomination": [ + "kilowatt" + ], + "human": { + "en": "kilowatt", + "nl": "kilowatt", + "de": "Kilowatt", + "ru": "киловатт" + } + }, + { + "canonicalDenomination": "mW", + "alternativeDenomination": [ + "megawatt" + ], + "human": { + "en": "megawatt", + "nl": "megawatt", + "de": "Megawatt", + "ru": "мегаватт" + } + } + ], + "eraseInvalidValues": true + } + ], + "allowMove": { + "enableRelocation": false, + "enableImproveAccuracy": true + }, + "deletion": { + "softDeletionTags": { + "and": [ + "amenity=", + "disused:amenity=charging_station" + ] + }, + "neededChangesets": 10 + }, + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "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" + } + ] + }, + "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" + } + } ] - }, - "iconOverlays": [ - { - "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", - { - "or": [ - "motorcar=yes", - "car=yes" - ] - } - ] - }, - "then": "circle:#fff;./assets/themes/charging_stations/car.svg", - "badge": true - } - ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "50,50,bottom" - }, - "color": { - "render": "#00f" - }, - "presets": [ - { - "tags": [ - "amenity=charging_station", - "motorcar=no", - "bicycle=yes", - "socket:typee=1" - ], - "title": { - "en": "charging station with a normal european wall plug (meant to charge electrical bikes)", - "nl": "laadpunt met gewone stekker(s) (bedoeld om electrische fietsen op te laden)" - }, - "preciseInput": { - "preferredBackground": "map" - } - }, - { - "tags": [ - "amenity=charging_station", - "motorcar=no", - "bicycle=yes" - ], - "title": { - "en": "charging station for e-bikes", - "nl": "oplaadpunt voor elektrische fietsen" - }, - "preciseInput": { - "preferredBackground": "map" - } - }, - { - "tags": [ - "amenity=charging_station", - "motorcar=yes", - "bicycle=no" - ], - "title": { - "en": "charging station for cars", - "nl": "oplaadstation voor elektrische auto's" - }, - "preciseInput": { - "preferredBackground": "map" - } - }, - { - "tags": [ - "amenity=charging_station" - ], - "title": { - "en": "charging station", - "nl": "oplaadstation" - }, - "preciseInput": { - "preferredBackground": "map" - } - } - ], - "wayHandling": 1, - "filter": [ - { - "id": "vehicle-type", - "options": [ - { - "question": { - "en": "All vehicle types", - "nl": "Alle voertuigen" - } - }, - { - "question": { - "en": "Charging station for bicycles", - "nl": "Oplaadpunten voor fietsen" - }, - "osmTags": "bicycle=yes" - }, - { - "question": { - "en": "Charging station for cars", - "nl": "Oplaadpunten voor auto's" - }, - "osmTags": { - "or": [ - "car=yes", - "motorcar=yes" - ] - } - } - ] - }, - { - "id": "working", - "options": [ - { - "question": { - "en": "Only working charging stations", - "nl": "Enkel werkende oplaadpunten" - }, - "osmTags": { - "and": [ - "operational_status!=broken", - "amenity=charging_station" - ] - } - } - ] - }, - { - "id": "connection_type", - "options": [ - { - "question": { - "en": "All connectors", - "nl": "Alle types" - } - }, - { - "question": { - "en": "Has a
Schuko wall plug without ground pin (CEE7/4 type F)
connector", - "nl": "Heeft een
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "osmTags": "socket:schuko~*" - }, - { - "question": { - "en": "Has a
European wall plug with ground pin (CEE7/4 type E)
connector", - "nl": "Heeft een
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "osmTags": "socket:typee~*" - }, - { - "question": { - "en": "Has a
Chademo
connector", - "nl": "Heeft een
Chademo
" - }, - "osmTags": "socket:chademo~*" - }, - { - "question": { - "en": "Has a
Type 1 with cable (J1772)
connector", - "nl": "Heeft een
Type 1 met kabel (J1772)
" - }, - "osmTags": "socket:type1_cable~*" - }, - { - "question": { - "en": "Has a
Type 1 without cable (J1772)
connector", - "nl": "Heeft een
Type 1 zonder kabel (J1772)
" - }, - "osmTags": "socket:type1~*" - }, - { - "question": { - "en": "Has a
Type 1 CCS (aka Type 1 Combo)
connector", - "nl": "Heeft een
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "osmTags": "socket:type1_combo~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger
connector", - "nl": "Heeft een
Tesla Supercharger
" - }, - "osmTags": "socket:tesla_supercharger~*" - }, - { - "question": { - "en": "Has a
Type 2 (mennekes)
connector", - "nl": "Heeft een
Type 2 (mennekes)
" - }, - "osmTags": "socket:type2~*" - }, - { - "question": { - "en": "Has a
Type 2 CCS (mennekes)
connector", - "nl": "Heeft een
Type 2 CCS (mennekes)
" - }, - "osmTags": "socket:type2_combo~*" - }, - { - "question": { - "en": "Has a
Type 2 with cable (mennekes)
connector", - "nl": "Heeft een
Type 2 met kabel (J1772)
" - }, - "osmTags": "socket:type2_cable~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger CCS (a branded type2_css)
connector", - "nl": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "osmTags": "socket:tesla_supercharger_ccs~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger (destination)
connector", - "nl": "Heeft een
Tesla Supercharger (destination)
" - }, - "osmTags": "socket:tesla_destination~*" - }, - { - "question": { - "en": "Has a
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
connector", - "nl": "Heeft een
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "osmTags": "socket:tesla_destination~*" - }, - { - "question": { - "en": "Has a
USB to charge phones and small electronics
connector", - "nl": "Heeft een
USB om GSMs en kleine electronica op te laden
" - }, - "osmTags": "socket:USB-A~*" - }, - { - "question": { - "en": "Has a
Bosch Active Connect with 3 pins and cable
connector", - "nl": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "osmTags": "socket:bosch_3pin~*" - }, - { - "question": { - "en": "Has a
Bosch Active Connect with 5 pins and cable
connector", - "nl": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "osmTags": "socket:bosch_5pin~*" - } - ] - } - ], - "units": [ - { - "appliesToKey": [ - "maxstay" - ], - "applicableUnits": [ - { - "canonicalDenomination": "minutes", - "canonicalDenominationSingular": "minute", - "alternativeDenomination": [ - "m", - "min", - "mins", - "minuten", - "mns" - ], - "human": { - "en": " minutes", - "nl": " minuten" - }, - "humanSingular": { - "en": " minute", - "nl": " minuut" - } - }, - { - "canonicalDenomination": "hours", - "canonicalDenominationSingular": "hour", - "alternativeDenomination": [ - "h", - "hrs", - "hours", - "u", - "uur", - "uren" - ], - "human": { - "en": " hours", - "nl": " uren" - }, - "humanSingular": { - "en": " hour", - "nl": " uur" - } - }, - { - "canonicalDenomination": "days", - "canonicalDenominationSingular": "day", - "alternativeDenomination": [ - "dys", - "dagen", - "dag" - ], - "human": { - "en": " days", - "nl": " day" - }, - "humanSingular": { - "en": " day", - "nl": " dag" - } - } - ] - }, - { - "appliesToKey": [ - "socket:schuko:voltage", - "socket:typee:voltage", - "socket:chademo:voltage", - "socket:type1_cable:voltage", - "socket:type1:voltage", - "socket:type1_combo:voltage", - "socket:tesla_supercharger:voltage", - "socket:type2:voltage", - "socket:type2_combo:voltage", - "socket:type2_cable:voltage", - "socket:tesla_supercharger_ccs:voltage", - "socket:tesla_destination:voltage", - "socket:tesla_destination:voltage", - "socket:USB-A:voltage", - "socket:bosch_3pin:voltage", - "socket:bosch_5pin:voltage" - ], - "applicableUnits": [ - { - "canonicalDenomination": "V", - "alternativeDenomination": [ - "v", - "volt", - "voltage", - "V", - "Volt" - ], - "human": { - "en": "Volts", - "nl": "volt" - } - } - ], - "eraseInvalidValues": true - }, - { - "appliesToKey": [ - "socket:schuko:current", - "socket:typee:current", - "socket:chademo:current", - "socket:type1_cable:current", - "socket:type1:current", - "socket:type1_combo:current", - "socket:tesla_supercharger:current", - "socket:type2:current", - "socket:type2_combo:current", - "socket:type2_cable:current", - "socket:tesla_supercharger_ccs:current", - "socket:tesla_destination:current", - "socket:tesla_destination:current", - "socket:USB-A:current", - "socket:bosch_3pin:current", - "socket:bosch_5pin:current" - ], - "applicableUnits": [ - { - "canonicalDenomination": "A", - "alternativeDenomination": [ - "a", - "amp", - "amperage", - "A" - ], - "human": { - "en": "A", - "nl": "A" - } - } - ], - "eraseInvalidValues": true - }, - { - "appliesToKey": [ - "socket:schuko:output", - "socket:typee:output", - "socket:chademo:output", - "socket:type1_cable:output", - "socket:type1:output", - "socket:type1_combo:output", - "socket:tesla_supercharger:output", - "socket:type2:output", - "socket:type2_combo:output", - "socket:type2_cable:output", - "socket:tesla_supercharger_ccs:output", - "socket:tesla_destination:output", - "socket:tesla_destination:output", - "socket:USB-A:output", - "socket:bosch_3pin:output", - "socket:bosch_5pin:output" - ], - "applicableUnits": [ - { - "canonicalDenomination": "kW", - "alternativeDenomination": [ - "kilowatt" - ], - "human": { - "en": "kilowatt", - "nl": "kilowatt" - } - }, - { - "canonicalDenomination": "mW", - "alternativeDenomination": [ - "megawatt" - ], - "human": { - "en": "megawatt", - "nl": "megawatt" - } - } - ], - "eraseInvalidValues": true - } - ], - "allowMove": { - "enableRelocation": false, - "enableImproveAccuracy": true - }, - "deletion": { - "softDeletionTags": { - "and": [ - "amenity=", - "disused:amenity=charging_station" - ] - }, - "neededChangesets": 10 - } } \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.protojson b/assets/layers/charging_station/charging_station.protojson index 8c7002024..3d0465068 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..743e47cd6 --- /dev/null +++ b/assets/layers/conflation/conflation.json @@ -0,0 +1,45 @@ +{ + "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", + "dasharray": { + "render": "", + "mappings": [ + { + "if": "resulting-geometry=yes", + "then": "6 6" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index a8b673f2f..ca4b7c18c 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -254,8 +254,7 @@ "if": "tactile_paving=incorrect", "then": { "en": "This crossing has tactile paving, but is not correct", - "nl": "Deze oversteekplaats heeft een geleidelijn, die incorrect is.", - "de": "Diese Kreuzung hat taktile Pflasterung, ist aber nicht korrekt" + "nl": "Deze oversteekplaats heeft een geleidelijn, die incorrect is." }, "hideInAnswer": true } @@ -279,8 +278,7 @@ "if": "button_operated=yes", "then": { "en": "This traffic light has a button to request green light", - "nl": "Dit verkeerslicht heeft een knop voor groen licht", - "de": "Diese Ampel hat eine Taste, um ein grünes Signal anzufordern" + "nl": "Dit verkeerslicht heeft een knop voor groen licht" } }, { @@ -288,7 +286,7 @@ "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" + "de": "Diese Ampel hat keine Taste, um ein grünes Signal anzufordern." } } ] @@ -367,5 +365,29 @@ } ] } + ], + "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 58807bdab..f5f64d797 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -54,8 +54,7 @@ "if": "cycleway=shared_lane", "then": { "nl": "Fietssuggestiestrook", - "en": "Shared lane", - "de": "Gemeinsame Fahrspur" + "en": "Shared lane" } }, { @@ -731,8 +730,7 @@ }, "render": { "en": "The carriage width of this road is {width:carriageway}m", - "nl": "De breedte van deze rijbaan in deze straat is {width:carriageway}m", - "de": "Die Fahrbahnbreite dieser Straße beträgt {width:carriageway}m" + "nl": "De breedte van deze rijbaan in deze straat is {width:carriageway}m" }, "freeform": { "key": "width:carriageway", @@ -744,8 +742,7 @@ }, "question": { "en": "What is the carriage width of this road (in meters)?
This is measured curb to curb and thus includes the width of parallell parking lanes", - "nl": "Hoe breed is de rijbaan in deze straat (in meters)?
Dit is
Meet dit van stoepsteen tot stoepsteen, dus inclusief een parallelle parkeerstrook", - "de": "Wie groß ist die Fahrbahnbreite dieser Straße (in Metern)?
Diese wird von Bordstein zu Bordstein gemessen und schließt daher die Breite von parallelen Parkspuren ein" + "nl": "Hoe breed is de rijbaan in deze straat (in meters)?
Dit is
Meet dit van stoepsteen tot stoepsteen, dus inclusief een parallelle parkeerstrook" }, "id": "width:carriageway" }, @@ -903,8 +900,7 @@ "id": "cycleway-traffic-signs-supplementary", "question": { "en": "Does the traffic sign D7 () have a supplementary sign?", - "nl": "Heeft het verkeersbord D7 () een onderbord?", - "de": "Hat das Verkehrszeichen D7 () ein Zusatzzeichen?" + "nl": "Heeft het verkeersbord D7 () een onderbord?" }, "condition": { "or": [ @@ -995,8 +991,7 @@ "id": "cycleway-traffic-signs-D7-supplementary", "question": { "en": "Does the traffic sign D7 () have a supplementary sign?", - "nl": "Heeft het verkeersbord D7 () een onderbord?", - "de": "Hat das Verkehrszeichen D7 () ein Zusatzzeichen?" + "nl": "Heeft het verkeersbord D7 () een onderbord?" }, "condition": { "or": [ @@ -1281,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 9af75d3f9..01a68444b 100644 --- a/assets/layers/etymology/etymology.json +++ b/assets/layers/etymology/etymology.json @@ -172,5 +172,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 4d663408d..724e676e7 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -92,8 +92,7 @@ ], "description": { "nl": "Een zaak waar je snel bediend wordt, vaak met de focus op afhalen. Zitgelegenheid is eerder beperkt (of zelfs afwezig)", - "en": "A food business concentrating on fast counter-only service and take-away food", - "de": "Ein Lebensmittelunternehmen, das sich auf schnellen Thekendienst und Essen zum Mitnehmen konzentriert" + "en": "A food business concentrating on fast counter-only service and take-away food" }, "preciseInput": { "preferredBackground": "map" @@ -571,8 +570,7 @@ "fr": "Apporter ses propres contenants n’est pas permis", "en": "Bringing your own container is not allowed", "ja": "独自の容器を持参することはできません", - "ru": "Приносить свою тару не разрешено", - "de": "Das Mitbringen eines eigenen Containers ist nicht erlaubt" + "ru": "Приносить свою тару не разрешено" } }, { @@ -581,8 +579,7 @@ "nl": "Je moet je eigen containers meenemen om je bestelling in mee te nemen.", "en": "You must bring your own container to order here.", "ja": "自身の容器が注文に必要。", - "fr": "Il est obligatoire d’apporter ses propres contenants", - "de": "Sie müssen Ihren eigenen Behälter mitbringen, um hier zu bestellen." + "fr": "Il est obligatoire d’apporter ses propres contenants" } } ], @@ -669,5 +666,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 fb5a32e15..8e6010525 100644 --- a/assets/layers/nature_reserve/nature_reserve.json +++ b/assets/layers/nature_reserve/nature_reserve.json @@ -304,8 +304,7 @@ "nl": "{curator} is de beheerder van dit gebied", "en": "{curator} is the curator of this nature reserve", "it": "{curator} è il curatore di questa riserva naturale", - "fr": "{curator} est en charge de la conservation de la réserve", - "de": "{curator} ist der Pfleger dieses Naturschutzgebietes" + "fr": "{curator} est en charge de la conservation de la réserve" }, "freeform": { "key": "curator", @@ -318,8 +317,7 @@ "nl": "Waar kan men naartoe emailen voor vragen en meldingen van dit natuurgebied?
Respecteer privacy - geef enkel persoonlijke emailadressen als deze elders zijn gepubliceerd", "en": "What email adress can one send to with questions and problems with this nature reserve?
Respect privacy - only fill out a personal email address if this is widely published", "it": "Qual è l’indirizzo email a cui scrivere per fare domande o segnalare problemi su questa riserva naturale?
Rispetta la privacy (compila l’indirizzo email personale solo se è stato reso pubblico)", - "fr": "À quelle adresse courriel peut-on envoyer des questions et des problèmes concernant cette réserve naturelle ?
Respecter la vie privée – renseignez une adresse électronique personnelle seulement si celle-ci est largement publiée", - "de": "An welche Email-Adresse kann man sich bei Fragen und Problemen zu diesem Naturschutzgebiet wenden?
Respektieren Sie die Privatsphäre - geben Sie nur dann eine persönliche Email-Adresse an, wenn diese allgemein bekannt ist" + "fr": "À quelle adresse courriel peut-on envoyer des questions et des problèmes concernant cette réserve naturelle ?
Respecter la vie privée – renseignez une adresse électronique personnelle seulement si celle-ci est largement publiée" }, "render": { "nl": "
{email}", @@ -342,8 +340,7 @@ "nl": "Waar kan men naartoe bellen voor vragen en meldingen van dit natuurgebied?
Respecteer privacy - geef enkel persoonlijke telefoonnummers als deze elders zijn gepubliceerd", "en": "What phone number can one call to with questions and problems with this nature reserve?
Respect privacy - only fill out a personal phone number address if this is widely published", "it": "Quale numero di telefono comporre per fare domande o segnalare problemi riguardanti questa riserva naturale?br/>Rispetta la privacy (inserisci il numero di telefono privato solo se questo è noto pubblicamente)", - "fr": "Quel numéro de téléphone peut-on appeler pour poser des questions et résoudre des problèmes concernant cette réserve naturelle ?
Respecter la vie privée – renseignez un numéro de téléphone personnel seulement si celui-ci est largement publié", - "de": "Welche Telefonnummer kann man bei Fragen und Problemen zu diesem Naturschutzgebiet anrufen?
Respektieren Sie die Privatsphäre - geben Sie nur eine Telefonnummer an, wenn diese allgemein bekannt ist" + "fr": "Quel numéro de téléphone peut-on appeler pour poser des questions et résoudre des problèmes concernant cette réserve naturelle ?
Respecter la vie privée – renseignez un numéro de téléphone personnel seulement si celui-ci est largement publié" }, "render": { "nl": "{phone}", @@ -466,5 +463,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 277f0fb02..6b4c6b11b 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": [ @@ -487,5 +487,32 @@ } ] } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/bookcases/bookcase.svg" + }, + "label": { + "mappings": [ + { + "if": "name~*", + "then": "
{name}
" + } + ] + }, + "location": [ + "point", + "centroid" + ] + }, + { + "color": { + "render": "#0000ff" + }, + "width": { + "render": "8" + } + } ] } \ No newline at end of file diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index ab9047833..c9d263d81 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -377,5 +377,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 295a6e936..1de9fc997 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -265,5 +265,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/street_lamps/bent_pole_1.svg b/assets/layers/street_lamps/bent_pole_1.svg new file mode 100644 index 000000000..ed33e232b --- /dev/null +++ b/assets/layers/street_lamps/bent_pole_1.svg @@ -0,0 +1,100 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/street_lamps/bent_pole_2.svg b/assets/layers/street_lamps/bent_pole_2.svg new file mode 100644 index 000000000..f41cdeaf9 --- /dev/null +++ b/assets/layers/street_lamps/bent_pole_2.svg @@ -0,0 +1,138 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/street_lamps/license_info.json b/assets/layers/street_lamps/license_info.json new file mode 100644 index 000000000..34ca9cd83 --- /dev/null +++ b/assets/layers/street_lamps/license_info.json @@ -0,0 +1,36 @@ +[ + { + "path": "bent_pole_1.svg", + "license": "CC0", + "authors": [ + "Robin van der Linde" + ], + "sources": [] + }, + { + "path": "bent_pole_2.svg", + "license": "CC0", + "authors": [ + "Robin van der Linde" + ], + "sources": [] + }, + { + "path": "straight_pole.svg", + "license": "CC0", + "authors": [ + "Robin van der Linde" + ], + "sources": [] + }, + { + "path": "street_lamp.svg", + "license": "CC0", + "authors": [ + "Yohan Boniface" + ], + "sources": [ + "https://github.com/hotosm/HDM-CartoCSS/blob/master/icons/poi/street_lamp.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/street_lamps/straight_pole.svg b/assets/layers/street_lamps/straight_pole.svg new file mode 100644 index 000000000..c59f755d6 --- /dev/null +++ b/assets/layers/street_lamps/straight_pole.svg @@ -0,0 +1,160 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/street_lamps/street_lamp.svg b/assets/layers/street_lamps/street_lamp.svg new file mode 100644 index 000000000..72602af3d --- /dev/null +++ b/assets/layers/street_lamps/street_lamp.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/layers/street_lamps/street_lamps.json b/assets/layers/street_lamps/street_lamps.json new file mode 100644 index 000000000..96a64b76f --- /dev/null +++ b/assets/layers/street_lamps/street_lamps.json @@ -0,0 +1,371 @@ +{ + "id": "street_lamps", + "name": { + "en": "Street Lamps", + "nl": "Straatlantaarns" + }, + "source": { + "osmTags": "highway=street_lamp" + }, + "minZoom": 16, + "title": { + "render": { + "en": "Street Lamp", + "nl": "Straatlantaarn" + }, + "mappings": [ + { + "if": "ref~*", + "then": { + "en": "Street Lamp {ref}", + "nl": "Straatlantaarn {ref}" + } + } + ] + }, + "mapRendering": [ + { + "location": "point", + "icon": "./assets/layers/street_lamps/street_lamp.svg", + "iconBadges": [ + { + "if": "light:colour~*", + "then": "circle:{light:colour}" + } + ], + "iconSize": "40,40,bottom" + } + ], + "presets": [ + { + "title": { + "en": "street lamp", + "nl": "straatlantaarn" + }, + "tags": [ + "highway=street_lamp" + ], + "preciseInput": true + } + ], + "tagRenderings": [ + { + "id": "ref", + "render": { + "en": "This street lamp has the reference number {ref}", + "nl": "Deze straatlantaarn heeft het nummer {ref}" + }, + "question": { + "en": "What is the reference number of this street lamp?", + "nl": "Wat is het nummer van deze straatlantaarn?" + }, + "freeform": { + "key": "ref" + } + }, + { + "id": "support", + "question": { + "en": "How is this street lamp mounted?", + "nl": "Hoe is deze straatlantaarn gemonteerd?" + }, + "mappings": [ + { + "if": "support=catenary", + "then": { + "en": "This lamp is suspended using cables", + "nl": "Deze lantaarn hangt aan kabels" + } + }, + { + "if": "support=ceiling", + "then": { + "en": "This lamp is mounted on a ceiling", + "nl": "Deze lantaarn hangt aan een plafond" + } + }, + { + "if": "support=ground", + "then": { + "en": "This lamp is mounted in the ground", + "nl": "Deze lantaarn zit in de grond" + } + }, + { + "if": "support=pedestal", + "then": { + "en": "This lamp is mounted on a short pole (mostly < 1.5m)", + "nl": "Deze lantaarn zit op een korte paal (meestal < 1.5m)" + } + }, + { + "if": "support=pole", + "then": { + "en": "This lamp is mounted on a pole", + "nl": "Deze lantaarn zit op een paal" + } + }, + { + "if": "support=wall", + "then": { + "en": "This lamp is mounted directly to the wall", + "nl": "Deze lantaarn hangt direct aan de muur" + } + }, + { + "if": "support=wall_mount", + "then": { + "en": "This lamp is mounted to the wall using a metal bar", + "nl": "Deze lantaarn hangt aan de muur met een metalen balk" + } + } + ] + }, + { + "id": "lamp_mount", + "question": { + "en": "How is this lamp mounted to the pole?", + "nl": "Hoe zit deze lantaarn aan de paal?" + }, + "condition": "support=pole", + "mappings": [ + { + "if": "lamp_mount=straight_mast", + "then": { + "en": "This lamp sits atop of a straight mast", + "nl": "Deze lantaarn zit boven op een rechte paal" + } + }, + { + "if": "lamp_mount=bent_mast", + "then": { + "en": "This lamp sits at the end of a bent mast", + "nl": "Deze lantaarn zit aan het eind van een gebogen paal" + } + } + ] + }, + { + "id": "method", + "question": { + "en": "What kind of lighting does this lamp use?", + "nl": "Wat voor verlichting gebruikt deze lantaarn?" + }, + "mappings": [ + { + "if": "light:method=electric", + "then": { + "en": "This lamp is lit electrically", + "nl": "Deze lantaarn is elektrisch verlicht" + }, + "hideInAnswer": true + }, + { + "if": "light:method=LED", + "then": { + "en": "This lamp uses LEDs", + "nl": "Deze lantaarn gebruikt LEDs" + } + }, + { + "if": "light:method=incandescent", + "then": { + "en": "This lamp uses incandescent lighting", + "nl": "Deze lantaarn gebruikt gloeilampen" + } + }, + { + "if": "light:method=halogen", + "then": { + "en": "This lamp uses halogen lighting", + "nl": "Deze lantaarn gebruikt halogeen verlichting" + } + }, + { + "if": "light:method=discharge", + "then": { + "en": "This lamp uses discharge lamps (unknown type)", + "nl": "Deze lantaarn gebruikt gasontladingslampen (onbekend type)" + } + }, + { + "if": "light:method=mercury", + "then": { + "en": "This lamp uses a mercury-vapour lamp (lightly blueish)", + "nl": "Deze lantaarn gebruikt een kwiklamp (enigszins blauwachtig)" + } + }, + { + "if": "light:method=metal-halide", + "then": { + "en": "This lamp uses metal-halide lamps (bright white)", + "nl": "Deze lantaarn gebruikt metaalhalidelampen" + } + }, + { + "if": "light:method=fluorescent", + "then": { + "en": "This lamp uses fluorescent lighting", + "nl": "Deze lantaarn gebruikt fluorescentieverlichting (TL en spaarlamp)" + } + }, + { + "if": "light:method=sodium", + "then": { + "en": "This lamp uses sodium lamps (unknown type)", + "nl": "Deze lantaarn gebruikt natriumlampen (onbekend type)" + } + }, + { + "if": "light:method=low_pressure_sodium", + "then": { + "en": "This lamp uses low pressure sodium lamps (monochrome orange)", + "nl": "Deze lantaarn gebruikt lagedruknatriumlampen (monochroom oranje)" + } + }, + { + "if": "light:method=high_pressure_sodium", + "then": { + "en": "This lamp uses high pressure sodium lamps (orange with white)", + "nl": "Deze lantaarn gebruikt hogedruknatriumlampen (oranje met wit)" + } + }, + { + "if": "light:method=gas", + "then": { + "en": "This lamp is lit using gas", + "nl": "Deze lantaarn wordt verlicht met gas" + } + } + ] + }, + { + "id": "colour", + "question": { + "en": "What colour light does this lamp emit?", + "nl": "Wat voor kleur licht geeft deze lantaarn?" + }, + "render": { + "en": "This lamp emits {light:colour} light", + "nl": "Deze lantaarn geeft {light:colour} licht" + }, + "freeform": { + "key": "light:colour", + "type": "color" + }, + "mappings": [ + { + "if": "light:colour=white", + "then": { + "en": "This lamp emits white light", + "nl": "Deze lantaarn geeft wit licht" + } + }, + { + "if": "light:colour=green", + "then": { + "en": "This lamp emits green light", + "nl": "Deze lantaarn geeft groen licht" + } + }, + { + "if": "light:colour=orange", + "then": { + "en": "This lamp emits orange light", + "nl": "Deze lantaarn geeft oranje licht" + } + } + ] + }, + { + "id": "count", + "render": { + "en": "This lamp has {light:count} fixtures", + "nl": "Deze lantaarn heeft {light:count} lampen" + }, + "question": { + "en": "How many fixtures does this light have?", + "nl": "Hoeveel lampen heeft deze lantaarn?" + }, + "condition": "support=pole", + "freeform": { + "key": "light:count", + "type": "pnat" + }, + "mappings": [ + { + "if": "light:count=1", + "then": { + "en": "This lamp has 1 fixture", + "nl": "Deze lantaarn heeft 1 lamp" + } + }, + { + "if": "light:count=2", + "then": { + "en": "This lamp has 2 fixtures", + "nl": "Deze lantaarn heeft 2 lampen" + } + } + ] + }, + { + "id": "lit", + "question": { + "en": "When is this lamp lit?", + "nl": "Wanneer is deze lantaarn verlicht?" + }, + "mappings": [ + { + "if": "light:lit=dusk-dawn", + "then": { + "en": "This lamp is lit at night", + "nl": "Deze lantaarn is 's nachts verlicht" + } + }, + { + "if": "light:lit=24/7", + "then": { + "en": "This lamp is lit 24/7", + "nl": "Deze lantaarn is 24/7 verlicht" + } + }, + { + "if": "light:lit=motion", + "then": { + "en": "This lamp is lit based on motion", + "nl": "Deze lantaarn is verlicht op basis van beweging" + } + }, + { + "if": "light:lit=demand", + "then": { + "en": "This lamp is lit based on demand (e.g. with a pushbutton)", + "nl": "Deze lantaarn is verlicht op verzoek (bijv. met een drukknop)" + } + } + ] + }, + { + "id": "direction", + "render": { + "en": "This lamp points towards {light:direction}", + "nl": "Deze lantaarn is gericht naar {light:direction}" + }, + "question": { + "en": "Where does this lamp point to?", + "nl": "Waar is deze lamp heengericht?" + }, + "condition": "light:count=1", + "freeform": { + "key": "light:direction", + "type": "direction" + } + } + ], + "deletion": true, + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": false + } +} \ 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 68808c0d9..ab2a0b380 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -181,8 +181,7 @@ "en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station, a public corridor or tunnel,...", "nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw, een publiek toegankelijke gang of tunnel...", "fr": "Une zone publique est surveillée, telle qu'une rue, un pont, une place, un parc, une gare, un couloir ou un tunnel public…", - "it": "Sorveglia un'area pubblica, come una strada, un ponte, una piazza, un parco, una stazione, un passaggio o un sottopasso pubblico, ...", - "de": "Überwacht wird ein öffentlicher Bereich, z. B. eine Straße, eine Brücke, ein Platz, ein Park, ein Bahnhof, ein öffentlicher Korridor oder Tunnel,..." + "it": "Sorveglia un'area pubblica, come una strada, un ponte, una piazza, un parco, una stazione, un passaggio o un sottopasso pubblico, ..." } }, { @@ -512,5 +511,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 1104d7bd1..efefb746b 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -406,8 +406,7 @@ "nl": "Erkend als erfgoed door een andere organisatie", "en": "Registered as heritage by a different organisation", "it": "Registrato come patrimonio da un’organizzazione differente", - "fr": "Enregistré comme patrimoine par une autre organisation", - "de": "Von einer anderen Organisation als Denkmal registriert" + "fr": "Enregistré comme patrimoine par une autre organisation" } }, { @@ -435,8 +434,7 @@ "nl": "Erkend als erfgoed door een andere organisatie", "en": "Registered as heritage by a different organisation", "it": "Registrato come patrimonio da un’organizzazione differente", - "fr": "Enregistré comme patrimoine par une autre organisation", - "de": "Von einer anderen Organisation als Denkmal registriert" + "fr": "Enregistré comme patrimoine par une autre organisation" }, "hideInAnswer": true } @@ -454,8 +452,7 @@ "en": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", "it": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", "ru": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", - "fr": "\"\"/ Identifiant Onroerend Erfgoed : {ref:OnroerendErfgoed}", - "de": "" + "fr": "\"\"/ Identifiant Onroerend Erfgoed : {ref:OnroerendErfgoed}" }, "question": { "nl": "Wat is het ID uitgegeven door Onroerend Erfgoed Vlaanderen?", @@ -618,5 +615,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 d16d88cfb..e56b69c37 100644 --- a/assets/layers/viewpoint/viewpoint.json +++ b/assets/layers/viewpoint/viewpoint.json @@ -72,5 +72,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 13b62e458..bbc51fc20 100644 --- a/assets/layers/visitor_information_centre/visitor_information_centre.json +++ b/assets/layers/visitor_information_centre/visitor_information_centre.json @@ -69,5 +69,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 3aee9637f..bed215af4 100644 --- a/assets/layers/waste_basket/waste_basket.json +++ b/assets/layers/waste_basket/waste_basket.json @@ -83,8 +83,7 @@ "if": "waste=sharps", "then": { "en": "A waste basket for needles and other sharp objects", - "nl": "Een vuilnisbak voor injectienaalden en andere scherpe voorwerpen", - "de": "Ein Abfalleimer für Nadeln und andere scharfe Gegenstände" + "nl": "Een vuilnisbak voor injectienaalden en andere scherpe voorwerpen" } } ] @@ -126,7 +125,7 @@ }, "then": { "en": "This waste basket does not have a dispenser for (dog) excrement bags", - "nl": "Deze vuilbak heeft geen verdeler voor hondenpoepzakjes", + "nl": "Deze vuilnisbak heeft geenverdeler voor hondenpoepzakjes", "de": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" } }, @@ -134,7 +133,7 @@ "if": "vending=", "then": { "en": "This waste basket does not have a dispenser for (dog) excrement bags", - "nl": "Deze vuilnisbak heeft geen verdeler voor hondenpoepzakjes", + "nl": "Deze vuilnisbaak heeft waarschijnlijk geen verdeler voor hondenpoepzakjes", "de": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" }, "hideInAnwer": true @@ -199,5 +198,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.json b/assets/themes/aed/aed.json index 8ca029588..78513e08b 100644 --- a/assets/themes/aed/aed.json +++ b/assets/themes/aed/aed.json @@ -13,7 +13,7 @@ "ru": "Открытая карта АВД (Автоматизированных внешних дефибрилляторов)", "ja": "オープンAEDマップ", "zh_Hant": "開放AED地圖", - "nb_NO": "Åpent AED-kart", + "nb_NO": "Åpne AED-kart", "sv": "Öppna AED-karta", "pl": "Otwórz mapę AED", "pt_BR": "Abrir mapa AED" 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/artwork/artwork.json b/assets/themes/artwork/artwork.json index 87b874611..86fe60be2 100644 --- a/assets/themes/artwork/artwork.json +++ b/assets/themes/artwork/artwork.json @@ -19,7 +19,7 @@ "en": "Welcome to Open Artwork Map, a map of statues, busts, grafittis and other artwork all over the world", "nl": "Welkom op de open kunstwerken-kaart, een kaart van standbeelden, bustes, graffiti en andere kunstwerken over de hele wereld", "fr": "Bienvenue sur la carte ouverte des œuvres d'art, une carte des statues, fresques, ... du monde entier", - "de": "Willkommen bei der Freien Kunstwerk-Karte, einer Karte von Statuen, Büsten, Grafitti, ... auf der ganzen Welt", + "de": "Willkommen bei der Freien Kunst-Karte, einer Karte mit Statuen, Büsten, Grafitti, ... auf der ganzen Welt", "id": "Selamat datang di Open Artwork Map, peta untuk patung, grafiti, dan karya seni lain di seluruh dunia", "it": "Benvenuto/a sulla mappa libera dell’arte, una mappa delle statue, i busti, i graffiti e le altre realizzazioni artistiche di tutto il mondo", "ru": "Добро пожаловать на Open Artwork Map, карту статуй, бюстов, граффити и других произведений искусства по всему миру", diff --git a/assets/themes/bookcases/bookcases.json b/assets/themes/bookcases/bookcases.json index 93a1d9f99..252f51048 100644 --- a/assets/themes/bookcases/bookcases.json +++ b/assets/themes/bookcases/bookcases.json @@ -29,7 +29,7 @@ "description": { "en": "A public bookcase is a small streetside cabinet, box, old phone boot or some other objects where books are stored. Everyone can place or take a book. This map aims to collect all these bookcases. You can discover new bookcases nearby and, with a free OpenStreetMap account, quickly add your favourite bookcases.", "nl": "Een boekenruilkast is een kastje waar iedereen een boek kan nemen of achterlaten. Op deze kaart kan je deze boekenruilkasten terugvinden en met een gratis OpenStreetMap-account, ook boekenruilkasten toevoegen of informatie verbeteren", - "de": "Ein Bücherschrank ist ein kleiner Schaltschrank, eine alte Telefonzelle oder eine andere Einrichtung, in der Bücher aufbewahrt werden. Jeder kann ein Buch hinstellen oder mitnehmen. Diese Karte zielt darauf ab, alle Orte mit Bücherschränken zu sammeln. Sie können neue Bücherschränke in der Nähe entdecken und mit einem kostenlosen OpenStreetMap-Konto schnell Ihre Lieblingsbücherschränke hinzufügen.", + "de": "Bücherschränke sind alte Schaltschränke, Telefonzellen oder andere Einrichtungen, zur Aufbewahrung von Büchern. Jeder kann Bücher abstellen oder mitnehmen. Die Karte zielt darauf ab, alle Orte mit Bücherschränken zu sammeln. Sie können neue Bücherschränke in der Nähe entdecken und mit einem kostenlosen OpenStreetMap-Konto schnell Ihre Lieblingsbücherschränke hinzufügen.", "fr": "Une microbibliothèques, également appelée boite à livre, est un élément de mobilier urbain (étagère, armoire, etc) dans lequel sont stockés des livres et autres objets en accès libre. Découvrez les boites à livres prêt de chez vous, ou ajouter en une nouvelle à l'aide de votre compte OpenStreetMap.", "ru": "Общественный книжный шкаф - это небольшой уличный шкаф, коробка, старый телефонный аппарат или другие предметы, где хранятся книги. Каждый может положить или взять книгу. Цель этой карты - собрать все эти книжные шкафы. Вы можете обнаружить новые книжные шкафы поблизости и, имея бесплатный аккаунт OpenStreetMap, быстро добавить свои любимые книжные шкафы.", "ja": "公共の本棚とは、本が保管されている小さな街角のキャビネット、箱、古い電話のトランク、その他の物のことです。誰でも本を置いたり持ったりすることができます。このマップは、すべての公共の本棚を収集することを目的としています。近くで新しい本棚を見つけることができ、無料のOpenStreetMapアカウントを使えば、お気に入りの本棚を簡単に追加できます。", 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 8f6d236f8..f45403b00 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -180,8 +180,7 @@ "nl": "Gebruik is betalend", "fr": "L’utilisation est payante", "pt_BR": "Você precisa pagar para usar", - "de": "Sie müssen für die Nutzung bezahlen", - "nb_NO": "Man må betale for bruk" + "de": "Sie müssen für die Nutzung bezahlen" } }, { @@ -336,8 +335,7 @@ "fr": "Cet endroit offre-t-il un accès à Internet ?", "zh_Hant": "這個地方有提網路連線嗎?", "pt_BR": "Este lugar fornece acesso a internet?", - "de": "Ist an diesem Ort ein Internetzugang vorhanden?", - "nb_NO": "Tilbyr dette stedet tilgang til Internett?" + "de": "Ist an diesem Ort ein Internetzugang vorhanden?" }, "mappings": [ { @@ -356,8 +354,7 @@ "fr": "Il y a un accès internet", "pt_BR": "Há acesso à internet", "de": "Internetzugang ist vorhanden", - "nl": "Er is internettoegang", - "nb_NO": "Det finnes tilgang til Internett" + "nl": "Er is internettoegang" } }, { @@ -377,8 +374,7 @@ "fr": "Il y a un accès internet", "pt_BR": "Há acesso à Internet", "de": "Internetzugang ist vorhanden", - "nl": "Er is internettoegang", - "nb_NO": "Det er tilgang til Internett" + "nl": "Er is internettoegang" }, "hideInAnswer": true }, @@ -397,8 +393,7 @@ "zh_Hant": "這裡沒有網路連線", "fr": "Il n’y a pas d’accès internet", "pt_BR": "Não há acesso à internet", - "de": "Kein Internetzugang vorhanden", - "nb_NO": "Det er tilgang til Internett" + "de": "Kein Internetzugang vorhanden" } } ] @@ -413,8 +408,7 @@ "zh_Hant": "你需要為網路連線付費嗎?", "fr": "L’accès internet est-il payant ?", "pt_BR": "Você tem que pagar pelo acesso à internet?", - "de": "Ist der Internetzugang gebührenpflichtig?", - "nb_NO": "Må man betale for tilgang til Internett?" + "de": "Ist der Internetzugang gebührenpflichtig?" }, "mappings": [ { @@ -431,8 +425,7 @@ "zh_Hant": "你需要額外付費來使用網路連線", "fr": "L’accès internet est en supplément", "pt_BR": "Você precisa pagar um extra pelo acesso à internet", - "de": "Der Internetzugang ist gebührenpflichtig", - "nb_NO": "Man må betale ekstra for tilgang til Internett" + "de": "Der Internetzugang ist gebührenpflichtig" } }, { @@ -449,8 +442,7 @@ "zh_Hant": "你不需要額外付費來使用網路連線", "fr": "L’accès internet est inclus", "pt_BR": "Você não precisa pagar um extra pelo acesso à internet", - "de": "Der Internetzugang ist kostenlos", - "nb_NO": "Man må ikke betale ekstra for tilgang til Internett" + "de": "Der Internetzugang ist kostenlos" } } ], @@ -643,28 +635,6 @@ "questions", "reviews" ], - "icon": { - "render": "circle:white;./assets/themes/campersite/caravan.svg", - "mappings": [ - { - "if": { - "and": [ - "fee=no" - ] - }, - "then": "circle:white;./assets/themes/campersite/caravan_green.svg" - } - ] - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -687,12 +657,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. ", + "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. " } } ], - "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", @@ -1059,18 +1060,6 @@ "id": "dumpstations-network" } ], - "icon": { - "render": "circle:white;./assets/themes/campersite/sanitary_dump_station.svg" - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "32,32,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -1091,6 +1080,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 61263d3c4..6a37b5e13 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -13,7 +13,7 @@ }, "description": { "nl": "Op deze kaart vind je verschillende klimgelegenheden, zoals klimzalen, bolderzalen en klimmen in de natuur", - "de": "Eine Karte mit verschiedenen Klettermöglichkeiten wie Kletterhallen, Kletterparks oder Felsen in der Natur.", + "de": "Eine Karte mit Klettermöglichkeiten wie Kletterhallen, Kletterparks oder Felsen.", "en": "On this map you will find various climbing opportunities such as climbing gyms, bouldering halls and rocks in nature.", "ru": "На этой карте вы найдете различные возможности для скалолазания, такие как скалодромы, залы для боулдеринга и скалы на природе.", "ja": "この地図には、自然の中のクライミングジム、ボルダリングホール、岩など、さまざまなクライミングの機会があります。", @@ -64,8 +64,7 @@ "ja": "クライミングクラブ", "zh_Hant": "攀岩社團", "nb_NO": "Klatreklubb", - "fr": "Club d’escalade", - "it": "Club di arrampicata" + "fr": "Club d’escalade" }, "minzoom": 10, "source": { @@ -95,8 +94,7 @@ "ja": "クライミングクラブ", "zh_Hant": "攀岩社團", "nb_NO": "Klatreklubb", - "fr": "Club d’escalade", - "it": "Club di arrampicata" + "fr": "Club d’escalade" }, "mappings": [ { @@ -107,8 +105,7 @@ "de": "Kletter-Organisation", "ja": "クライミングNGO", "zh_Hant": "攀岩 NGO", - "fr": "Association d’escalade", - "it": "Associazione di arrampicata" + "fr": "Association d’escalade" } } ] @@ -120,8 +117,7 @@ "ja": "クライミングクラブや団体", "zh_Hant": "攀岩社團或組織", "nb_NO": "En klatreklubb eller organisasjoner", - "fr": "Club ou association d’escalade", - "it": "Un club o associazione di arrampacata" + "fr": "Club ou association d’escalade" }, "tagRenderings": [ { @@ -134,16 +130,14 @@ "id": "{name}", "ru": "{name}", "ja": "{name}", - "zh_Hant": "{name}", - "it": "{name}" + "zh_Hant": "{name}" }, "question": { "en": "What is the name of this climbing club or NGO?", "de": "Wie lautet der Name dieses Vereins oder Organisation?", "nl": "Wat is de naam van deze klimclub?", "ja": "この登山クラブやNGOの名前は何ですか?", - "fr": "Quel est le nom du club ou de l’association ?", - "it": "Qual è il nome di questo club o associazione di arrampicata?" + "fr": "Quel est le nom du club ou de l’association ?" }, "freeform": { "key": "name" @@ -159,25 +153,6 @@ "phone", "opening_hours" ], - "icon": { - "render": "./assets/themes/climbing/club.svg" - }, - "iconOverlays": [ - { - "if": "opening_hours~*", - "then": "isOpen", - "badge": true - } - ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -191,8 +166,7 @@ "ja": "クライミングクラブ", "nb_NO": "Klatreklubb", "ru": "Клуб скалолазания", - "fr": "Club d’escalade", - "it": "Club di arrampicata" + "fr": "Club d’escalade" }, "description": { "de": "Ein Kletterverein", @@ -201,8 +175,7 @@ "ja": "クライミングクラブ", "nb_NO": "En klatreklubb", "ru": "Клуб скалолазания", - "fr": "Un club d’escalade", - "it": "Un club di arrampicata" + "fr": "Un club d’escalade" } }, { @@ -215,20 +188,36 @@ "en": "Climbing NGO", "nl": "Een klimorganisatie", "ja": "クライミングNGO", - "fr": "Association d’escalade", - "it": "Associazione di arrampicata" + "fr": "Association d’escalade" }, "description": { "de": "Eine Organisation, welche sich mit dem Klettern beschäftigt", "nl": "Een VZW die werkt rond klimmen", "en": "A NGO working around climbing", "ja": "登山に関わるNGO", - "fr": "Une association d’escalade", - "it": "Un’associazione che ha a che fare con l’arrampicata" + "fr": "Une association d’escalade" } } ], - "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", @@ -237,8 +226,7 @@ "en": "Climbing gyms", "nl": "Klimzalen", "ja": "クライミングジム", - "fr": "Salle d’escalade", - "it": "Palestre di arrampicata" + "fr": "Salle d’escalade" }, "minzoom": 10, "source": { @@ -255,8 +243,7 @@ "de": "Kletterhalle", "en": "Climbing gym", "ja": "クライミングジム", - "fr": "Salle d’escalade", - "it": "Palestra di arrampicata" + "fr": "Salle d’escalade" }, "mappings": [ { @@ -266,8 +253,7 @@ "de": "Kletterhalle {name}", "en": "Climbing gym {name}", "ja": "クライミングジム{name}", - "fr": "Salle d’escalade {name}", - "it": "Palestra di arrampicata {name}" + "fr": "Salle d’escalade {name}" } } ] @@ -277,8 +263,7 @@ "en": "A climbing gym", "ja": "クライミングジム", "nl": "Een klimzaal", - "fr": "Une salle d’escalade", - "it": "Una palestra di arrampicata" + "fr": "Une salle d’escalade" }, "tagRenderings": [ "images", @@ -296,16 +281,14 @@ "fr": "{name}", "id": "{name}", "ru": "{name}", - "ja": "{name}", - "it": "{name}" + "ja": "{name}" }, "question": { "en": "What is the name of this climbing gym?", "nl": "Wat is de naam van dit Klimzaal?", "de": "Wie heißt diese Kletterhalle?", "ja": "このクライミングジムは何という名前ですか?", - "fr": "Quel est le nom de la salle d’escalade ?", - "it": "Qual è il nome di questa palestra di arrampicata?" + "fr": "Quel est le nom de la salle d’escalade ?" }, "freeform": { "key": "name" @@ -318,21 +301,25 @@ "opening_hours", "reviews" ], - "icon": { - "render": "./assets/themes/climbing/climbing_gym.svg" - }, - "iconOverlays": [ + "mapRendering": [ { - "if": "opening_hours~*", - "then": "isOpen", - "badge": true + "icon": { + "render": "./assets/themes/climbing/climbing_gym.svg" + }, + "iconBadges": [ + { + "if": "opening_hours~*", + "then": "isOpen" + } + ], + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] } - ], - "width": "0", - "iconSize": { - "render": "40,40,center" - }, - "wayHandling": 1 + ] }, { "id": "climbing_route", @@ -342,8 +329,7 @@ "nl": "Klimroute", "ja": "登坂ルート", "nb_NO": "Klatreruter", - "fr": "Voies d’escalade", - "it": "Vie di arrampicata" + "fr": "Voies d’escalade" }, "minzoom": 18, "source": { @@ -394,8 +380,7 @@ "id": "{name}", "ru": "{name}", "ja": "{name}", - "it": "{name}", - "nb_NO": "{name}" + "it": "{name}" }, "question": { "en": "What is the name of this climbing route?", @@ -403,8 +388,7 @@ "nl": "Hoe heet deze klimroute?", "ja": "この登坂ルートの名前は何ですか?", "it": "Come si chiama questa via di arrampicata?", - "fr": "Quel est le nom de cette voie d’escalade ?", - "nb_NO": "Hva er navnet på denne klatreruten?" + "fr": "Quel est le nom de cette voie d’escalade ?" }, "freeform": { "key": "name" @@ -423,8 +407,7 @@ "nl": "Deze klimroute heeft geen naam", "ja": "この登坂ルートには名前がありません", "it": "Questa via di arrampicata non ha un nome", - "fr": "Cette voie n’a pas de nom", - "nb_NO": "Denne klatreruten har ikke noe navn" + "fr": "Cette voie n’a pas de nom" } } ], @@ -436,8 +419,7 @@ "nl": "Hoe lang is deze klimroute (in meters)?", "it": "Quanto è lunga questa via di arrampicata (in metri)?", "fr": "Quelle est la longueur de cette voie (en mètres) ?", - "de": "Wie lang ist diese Kletterroute (in Metern)?", - "nb_NO": "Hvor mange meter er klatreruten?" + "de": "Wie lang ist diese Kletterroute (in Metern)?" }, "render": { "de": "Diese Route ist {canonical(climbing:length)} lang", @@ -479,14 +461,12 @@ "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?", - "it": "Quanti bulloni sono presenti in questo percorso prima di arrivare alla moulinette?" + "de": "Wie viele Haken gibt es auf dieser Kletterroute bevor der Umlenker bzw. Standhaken erreicht ist?" }, "render": { "en": "This route has {climbing:bolts} bolts", "fr": "Cette voie a {climbing:bolts} prises", - "de": "Diese Kletterroute hat {climbing:bolts} Haken", - "it": "Questo percorso ha {climbing:bolts} bulloni" + "de": "Diese Kletterroute hat {climbing:bolts} Haken" }, "freeform": { "key": "climbing:bolts", @@ -501,8 +481,7 @@ "then": { "en": "This route is not bolted", "fr": "Cette voie n’a pas de prises", - "de": "Auf dieser Kletterroute sind keine Haken vorhanden", - "it": "In questo percorso non sono presenti bulloni" + "de": "Auf dieser Kletterroute sind keine Haken vorhanden" }, "hideInAnswer": true }, @@ -511,8 +490,7 @@ "then": { "en": "This route is not bolted", "fr": "Cette voie n’a pas de prises", - "de": "Auf dieser Kletterroute sind keine Haken vorhanden", - "it": "In questo percorso non sono presenti bulloni" + "de": "Auf dieser Kletterroute sind keine Haken vorhanden" } } ], @@ -529,8 +507,7 @@ { "render": { "en": "The rock type is {_embedding_features_with_rock:rock} as stated on the surrounding crag", - "fr": "Le type de roche est {_embedding_features_with_rock:rock} selon le mur", - "it": "Il tipo di roccia è {_embedding_features_with_rock:rock} come dichiarato sul muro circostante" + "fr": "Le type de roche est {_embedding_features_with_rock:rock} selon le mur" }, "freeform": { "key": "_embedding_features_with_rock:rock" @@ -539,26 +516,13 @@ }, "reviews" ], - "icon": { - "render": "circle:white;./assets/themes/climbing/climbing_route.svg" - }, - "width": { - "render": "4" - }, - "iconSize": { - "render": "28,28,center" - }, - "color": { - "render": "#0f0" - }, "presets": [ { "title": { "en": "Climbing route", "nl": "Klimroute", "fr": "Voie d’escalade", - "de": "Kletterroute", - "it": "Via di arrampicata" + "de": "Kletterroute" }, "tags": [ "sport=climbing", @@ -566,7 +530,28 @@ ] } ], - "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", @@ -575,8 +560,7 @@ "de": "Klettermöglichkeiten", "en": "Climbing opportunities", "ja": "登坂教室", - "fr": "Opportunité d’escalade", - "it": "Opportunità di arrampicata" + "fr": "Opportunité d’escalade" }, "minzoom": 10, "source": { @@ -597,16 +581,14 @@ "de": "Klettermöglichkeit", "ja": "登坂教室", "nb_NO": "Klatremulighet", - "fr": "Opportunité d’escalade", - "it": "Opportunità di arrampicata" + "fr": "Opportunité d’escalade" }, "mappings": [ { "if": "climbing=crag", "then": { "en": "Climbing crag {name}", - "fr": "Mur d’escalade {name}", - "it": "Muro da arrampicata {name}" + "fr": "Mur d’escalade {name}" } }, { @@ -625,8 +607,7 @@ "en": "Climbing area {name}", "nl": "Klimsite {name}", "fr": "Zone d’escalade {name}", - "de": "Klettergebiet {name}", - "it": "Area di arrampicata {name}" + "de": "Klettergebiet {name}" } }, { @@ -640,8 +621,7 @@ "en": "Climbing site", "nl": "Klimsite", "fr": "Site d’escalade", - "de": "Klettergebiet", - "it": "Sito di arrampicata" + "de": "Klettergebiet" } }, { @@ -650,8 +630,7 @@ "nl": "Klimgelegenheid {name}", "en": "Climbing opportunity {name}", "fr": "Opportunité d’escalade {name}", - "de": "Klettermöglichkeit {name}", - "it": "Opportunità di arrampicata {name}" + "de": "Klettermöglichkeit {name}" } } ] @@ -662,8 +641,7 @@ "en": "A climbing opportunity", "ja": "登坂教室", "nb_NO": "En klatremulighet", - "fr": "Opportunité d’escalade", - "it": "Un’opportunità di arrampicata" + "fr": "Opportunité d’escalade" }, "tagRenderings": [ "images", @@ -676,8 +654,7 @@ "render": { "en": "

Length overview

{histogram(_length_hist)}", "fr": "

Résumé de longueur

{histogram(_length_hist)}", - "de": "

Längenübersicht

{histogramm(_length_hist)}", - "it": "

Riassunto della lunghezza

{histogram(_length_hist)}" + "de": "

Längenübersicht

{histogramm(_length_hist)}" }, "condition": "_length_hist!~\\[\\]", "id": "Contained routes length hist" @@ -686,8 +663,7 @@ "render": { "en": "

Difficulties overview

{histogram(_difficulty_hist)}", "fr": "

Résumé des difficultés

{histogram(_difficulty_hist)}", - "de": "

Schwierigkeitsübersicht

{histogram(_difficulty_hist)}", - "it": "

Riassunto delle difficoltà

{histogram(_difficulty_hist)}" + "de": "

Schwierigkeitsübersicht

{histogram(_difficulty_hist)}" }, "condition": "_difficulty_hist!~\\[\\]", "id": "Contained routes hist" @@ -695,8 +671,7 @@ { "render": { "en": "

Contains {_contained_climbing_routes_count} routes

    {_contained_climbing_routes}
", - "fr": "

Contient {_contained_climbing_routes_count} voies

    {_contained_climbing_routes}
", - "it": "

Contiene {_contained_climbing_routes_count} vie

    {_contained_climbing_routes}
" + "fr": "

Contient {_contained_climbing_routes_count} voies

    {_contained_climbing_routes}
" }, "condition": "_contained_climbing_routes~*", "id": "Containe {_contained_climbing_routes_count} routes" @@ -710,16 +685,14 @@ "fr": "{name}", "id": "{name}", "ru": "{name}", - "ja": "{name}", - "it": "{name}" + "ja": "{name}" }, "question": { "en": "What is the name of this climbing opportunity?", "nl": "Wat is de naam van dit Klimgelegenheid?", "de": "Wie heißt diese Klettergelegenheit?", "ja": "この登坂教室の名前は何ですか?", - "fr": "Quel est le nom de ce site ?", - "it": "Qual è il nome di questa opportunità di arrampicata?" + "fr": "Quel est le nom de ce site ?" }, "freeform": { "key": "name" @@ -737,8 +710,7 @@ "nl": "Dit Klimgelegenheid heeft geen naam", "de": "Diese Klettergelegenheit hat keinen Namen", "ja": "この登坂教室には名前がついていない", - "fr": "Ce site n’a pas de nom", - "it": "Questa opportunità di arrampicata non ha un nome" + "fr": "Ce site n’a pas de nom" } } ], @@ -752,16 +724,14 @@ "then": { "en": "A climbing boulder - a single rock or cliff with one or a few climbing routes which can be climbed safely without rope", "fr": "Rocher d’escalade, rocher avec une ou peu de voie permettant d’escalader sans corde", - "de": "Ein Kletterfelsen - ein einzelner Felsen oder eine Klippe mit einer oder wenigen Kletterrouten, die ohne Seil sicher bestiegen werden können", - "it": "Un masso per arrampicata (una singola roccia o falesia con una o poche vie di arrampicata che possono essere scalate in sicurezza senza una corda)" + "de": "Ein Kletterfelsen - ein einzelner Felsen oder eine Klippe mit einer oder wenigen Kletterrouten, die ohne Seil sicher bestiegen werden können" } }, { "if": "climbing=crag", "then": { "en": "A climbing crag - a single rock or cliff with at least a few climbing routes", - "fr": "Mur d’escalade, rocher avec plusieurs voies d’escalades", - "it": "Un muro da arrampicata (un singolo masso o falesia con almeno qualche via per arrampicata)" + "fr": "Mur d’escalade, rocher avec plusieurs voies d’escalades" } }, { @@ -775,14 +745,12 @@ "question": { "en": "What is the rock type here?", "fr": "Quel est le type de roche ?", - "de": "Welchen Gesteinstyp gibt es hier?", - "it": "Qual è il tipo di roccia qua?" + "de": "Welchen Gesteinstyp gibt es hier?" }, "render": { "en": "The rock type is {rock}", "fr": "La roche est du {rock}", - "de": "Der Gesteinstyp ist {rock}", - "it": "Il tipo di roccia è {rock}" + "de": "Der Gesteinstyp ist {rock}" }, "freeform": { "key": "rock" @@ -794,8 +762,7 @@ "en": "Limestone", "nl": "Kalksteen", "fr": "Calcaire", - "de": "Kalkstein", - "it": "Calcare" + "de": "Kalkstein" } } ], @@ -810,18 +777,6 @@ }, "reviews" ], - "icon": { - "render": "./assets/themes/climbing/climbing_no_rope.svg" - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#d38d5fAA" - }, "presets": [ { "tags": [ @@ -833,8 +788,7 @@ "de": "Klettermöglichkeit", "ja": "登坂教室", "nb_NO": "Klatremulighet", - "fr": "Opportunité d’escalade", - "it": "Opportunità di arrampicata" + "fr": "Opportunité d’escalade" }, "description": { "nl": "Een klimgelegenheid", @@ -842,12 +796,10 @@ "en": "A climbing opportunity", "ja": "登坂教室", "nb_NO": "En klatremulighet", - "fr": "Opportunité d’escalade", - "it": "Un’opportunità di arrampicata" + "fr": "Opportunité d’escalade" } } ], - "wayHandling": 2, "calculatedTags": [ "_contained_climbing_routes_properties=feat.overlapWith('climbing_route').map(f => f.feat.properties).map(p => {return {id: p.id, name: p.name, 'climbing:grade:french': p['climbing:grade:french'], 'climbing:length': p['climbing:length']} })", "_contained_climbing_routes=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => `
  • ${p.name ?? 'climbing route'} (${p['climbing:grade:french'] ?? 'unknown difficulty'}, ${p['climbing:length'] ?? 'unkown length'} meter)
  • `).join('')", @@ -855,6 +807,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" + } + } ] }, { @@ -865,8 +839,7 @@ "en": "Climbing opportunities?", "ja": "登坂教室?", "nb_NO": "Klatremuligheter?", - "fr": "Opportunités d’escalade ?", - "it": "Opportunità di arrampicata?" + "fr": "Opportunités d’escalade ?" }, "minzoom": 19, "source": { @@ -893,8 +866,7 @@ "de": "Klettermöglichkeit?", "ja": "登坂教室?", "nb_NO": "Klatremulighet?", - "fr": "Opportunité d’escalade ?", - "it": "Opportunità di arrampicata?" + "fr": "Opportunité d’escalade ?" } }, "description": { @@ -903,8 +875,7 @@ "en": "A climbing opportunity?", "ja": "登坂教室?", "nb_NO": "En klatremulighet?", - "fr": "Opportunité d’escalade ?", - "it": "Un’opportunità di arrampicata?" + "fr": "Opportunité d’escalade ?" }, "tagRenderings": [ { @@ -921,8 +892,7 @@ "id": "{name}", "ru": "{name}", "ja": "{name}", - "nl": "{name}", - "it": "{name}" + "nl": "{name}" }, "condition": "name~*" }, @@ -933,8 +903,7 @@ "de": "Kann hier geklettert werden?", "ja": "ここで登坂はできますか?", "nb_NO": "Er klatring mulig her?", - "fr": "Est-il possible d’escalader ici ?", - "it": "È possibile arrampicarsi qua?" + "fr": "Est-il possible d’escalader ici ?" }, "mappings": [ { @@ -949,8 +918,7 @@ "ja": "ここでは登ることができない", "nb_NO": "Klatring er ikke mulig her", "nl": "Klimmen is hier niet mogelijk", - "fr": "Escalader n’est pas possible", - "it": "Non è possibile arrampicarsi qua" + "fr": "Escalader n’est pas possible" }, "hideInAnswer": true }, @@ -966,8 +934,7 @@ "ja": "ここでは登ることができる", "nb_NO": "Klatring er mulig her", "nl": "Klimmen is hier niet toegelaten", - "fr": "Escalader est possible", - "it": "È possibile arrampicarsi qua" + "fr": "Escalader est possible" } }, { @@ -978,21 +945,28 @@ "ja": "ここでは登ることができない", "nb_NO": "Klatring er ikke mulig her", "nl": "Klimmen is hier niet toegelaten", - "fr": "Escalader n’est pas possible", - "it": "Non è possibile arrampicarsi qua" + "fr": "Escalader n’est pas possible" } } ] } ], - "icon": "./assets/themes/climbing/climbing_unknown.svg", - "width": { - "render": "2" - }, - "color": { - "render": "#ddff55AA" - }, - "wayHandling": 0 + "mapRendering": [ + { + "icon": "./assets/themes/climbing/climbing_unknown.svg", + "location": [ + "point" + ] + }, + { + "color": { + "render": "#ddff55AA" + }, + "width": { + "render": "2" + } + } + ] } ], "overrideAll": { @@ -1047,8 +1021,7 @@ "nl": " meter", "fr": " mètres", "de": " Meter", - "eo": " metro", - "it": " metri" + "eo": " metro" }, "default": true }, @@ -1063,8 +1036,7 @@ "nl": " voet", "fr": " pieds", "de": " Fuß", - "eo": " futo", - "it": " piedi" + "eo": " futo" } } ] @@ -1079,8 +1051,7 @@ "ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?", "nl": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?", "ru": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?", - "fr": "Existe-t’il un site avec plus d’informations (ex : topographie) ?", - "it": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?" + "fr": "Existe-t’il un site avec plus d’informations (ex : topographie) ?" }, "condition": { "and": [ @@ -1104,8 +1075,7 @@ "then": { "en": "The containing feature states that this is publicly accessible
    {_embedding_feature:access:description}", "nl": "Een omvattend element geeft aan dat dit publiek toegangkelijk is
    {_embedding_feature:access:description}", - "fr": "L’élément englobant indique un accès libre
    {_embedding_feature:access:description}", - "it": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile
    {_embedding_feature:access:description}" + "fr": "L’élément englobant indique un accès libre
    {_embedding_feature:access:description}" } }, { @@ -1113,24 +1083,21 @@ "then": { "en": "The containing feature states that a permit is needed to access
    {_embedding_feature:access:description}", "nl": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen
    {_embedding_feature:access:description}", - "fr": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire
    {_embedding_feature:access:description}", - "it": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi
    {_embedding_feature:access:description}" + "fr": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire
    {_embedding_feature:access:description}" } }, { "if": "_embedding_feature:access=customers", "then": { "en": "The containing feature states that this is only accessible to customers
    {_embedding_feature:access:description}", - "fr": "L’élément englobant indique que l’accès est réservés aux clients
    {_embedding_feature:access:description}", - "it": "L’ elemento che lo contiene indica che è accessibile solo ai clienti
    {_embedding_feature:access:description}" + "fr": "L’élément englobant indique que l’accès est réservés aux clients
    {_embedding_feature:access:description}" } }, { "if": "_embedding_feature:access=members", "then": { "en": "The containing feature states that this is only accessible to club members
    {_embedding_feature:access:description}", - "fr": "L’élément englobant indique que l’accès est réservé aux membres
    {_embedding_feature:access:description}", - "it": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club
    {_embedding_feature:access:description}" + "fr": "L’élément englobant indique que l’accès est réservé aux membres
    {_embedding_feature:access:description}" } }, { @@ -1145,8 +1112,7 @@ "question": { "en": "Who can access here?", "fr": "Qui peut y accéder ?", - "de": "Wer hat hier Zugang?", - "it": "Chi può accedervi?" + "de": "Wer hat hier Zugang?" }, "mappings": [ { @@ -1154,8 +1120,7 @@ "then": { "en": "Publicly accessible to anyone", "fr": "Libre d’accès", - "de": "Öffentlich zugänglich für jedermann", - "it": "Pubblicamente accessibile a chiunque" + "de": "Öffentlich zugänglich für jedermann" } }, { @@ -1163,8 +1128,7 @@ "then": { "en": "You need a permit to access here", "fr": "Une autorisation est nécessaire", - "de": "Zugang nur mit Genehmigung", - "it": "È necessario avere un’autorizzazione per entrare" + "de": "Zugang nur mit Genehmigung" } }, { @@ -1172,8 +1136,7 @@ "then": { "en": "Only custumers", "fr": "Réservé aux clients", - "de": "Nur für Kunden", - "it": "Riservato ai clienti" + "de": "Nur für Kunden" } }, { @@ -1182,8 +1145,7 @@ "en": "Only club members", "ru": "Только членам клуба", "fr": "Réservé aux membres", - "de": "Nur für Vereinsmitglieder", - "it": "Riservato ai membri del club" + "de": "Nur für Vereinsmitglieder" } }, { @@ -1225,8 +1187,7 @@ "en": "The routes are {canonical(climbing:length)} long on average", "nl": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang", "ja": "ルートの長さは平均で{canonical(climbing:length)}です", - "fr": "Les voies font {canonical(climbing:length)} de long en moyenne", - "it": "Le vie sono lunghe mediamente {canonical(climbing:length)}" + "fr": "Les voies font {canonical(climbing:length)} de long en moyenne" }, "condition": { "and": [ @@ -1249,8 +1210,7 @@ "en": "What is the (average) length of the routes in meters?", "nl": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?", "ja": "ルートの(平均)長さはメートル単位でいくつですか?", - "fr": "Quelle est la longueur moyenne des voies en mètres ?", - "it": "Quale è la lunghezza (media) delle vie in metri?" + "fr": "Quelle est la longueur moyenne des voies en mètres ?" }, "freeform": { "key": "climbing:length", @@ -1264,16 +1224,14 @@ "en": "What is the level of the easiest route here, accoring to the french classification system?", "nl": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?", "ja": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?", - "fr": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?", - "it": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?" + "fr": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?" }, "render": { "de": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)", "en": "The minimal difficulty is {climbing:grade:french:min} according to the french/belgian system", "nl": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem", "ja": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です", - "fr": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge", - "it": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga" + "fr": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge" }, "freeform": { "key": "climbing:grade:french:min" @@ -1299,16 +1257,14 @@ "en": "What is the level of the most difficult route here, accoring to the french classification system?", "nl": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?", "ja": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?", - "fr": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?", - "it": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?" + "fr": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?" }, "render": { "de": "Die schwerste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)", "en": "The maximal difficulty is {climbing:grade:french:max} according to the french/belgian system", "nl": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem", "ja": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です", - "fr": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge", - "it": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga" + "fr": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge" }, "freeform": { "key": "climbing:grade:french:max" @@ -1335,8 +1291,7 @@ "nl": "Is het mogelijk om hier te bolderen?", "ja": "ここでボルダリングはできますか?", "nb_NO": "Er buldring mulig her?", - "fr": "L’escalade de bloc est-elle possible ici ?", - "it": "È possibile praticare ‘bouldering’ qua?" + "fr": "L’escalade de bloc est-elle possible ici ?" }, "mappings": [ { @@ -1347,8 +1302,7 @@ "nl": "Bolderen kan hier", "ja": "ボルダリングはここで可能です", "nb_NO": "Buldring er mulig her", - "fr": "L’escalade de bloc est possible", - "it": "L’arrampicata su massi è possibile qua" + "fr": "L’escalade de bloc est possible" } }, { @@ -1359,8 +1313,7 @@ "nl": "Bolderen kan hier niet", "ja": "ここではボルダリングはできません", "nb_NO": "Buldring er ikke mulig her", - "fr": "L’escalade de bloc n’est pas possible", - "it": "L’arrampicata su massi non è possibile qua" + "fr": "L’escalade de bloc n’est pas possible" } }, { @@ -1370,8 +1323,7 @@ "en": "Bouldering is possible, allthough there are only a few routes", "nl": "Bolderen kan hier, maar er zijn niet zoveel routes", "ja": "ボルダリングは可能ですが、少しのルートしかありません", - "fr": "L’escalade de bloc est possible sur des voies précises", - "it": "L’arrampicata su massi è possibile anche se su poche vie" + "fr": "L’escalade de bloc est possible sur des voies précises" } }, { @@ -1381,8 +1333,7 @@ "en": "There are {climbing:boulder} boulder routes", "nl": "Er zijn hier {climbing:boulder} bolderroutes", "ja": "{climbing:boulder} ボルダールートがある", - "fr": "Il y a {climbing:boulder} voies d’escalade de bloc", - "it": "Sono presenti {climbing:boulder} vie di arrampicata su massi" + "fr": "Il y a {climbing:boulder} voies d’escalade de bloc" }, "hideInAnswer": true } @@ -1407,8 +1358,7 @@ "en": "Is toprope climbing possible here?", "nl": "Is het mogelijk om hier te toprope-klimmen?", "ja": "ここでtoprope登坂はできますか?", - "fr": "Est-il possible d’escalader à la moulinette ?", - "it": "È possibile arrampicarsi con la corda dall’alto qua?" + "fr": "Est-il possible d’escalader à la moulinette ?" }, "mappings": [ { @@ -1418,8 +1368,7 @@ "en": "Toprope climbing is possible here", "nl": "Toprope-klimmen kan hier", "ja": "ここでToprope登坂ができます", - "fr": "L’escalade à la moulinette est possible", - "it": "È possibile arrampicarsi con moulinette qua" + "fr": "L’escalade à la moulinette est possible" } }, { @@ -1429,8 +1378,7 @@ "en": "Toprope climbing is not possible here", "nl": "Toprope-klimmen kan hier niet", "ja": "ここではToprope登坂はできません", - "fr": "L’escalade à la moulinette n’est pas possible", - "it": "Non è possibile arrampicarsi con moulinette qua" + "fr": "L’escalade à la moulinette n’est pas possible" } }, { @@ -1440,8 +1388,7 @@ "en": "There are {climbing:toprope} toprope routes", "nl": "Er zijn hier {climbing:toprope} toprope routes", "ja": "{climbing:toprope} 登坂ルートがある", - "fr": "{climbing:toprope} voies sont équipées de moulinettes", - "it": "Sono presenti {climbing:toprope} vie con moulinette" + "fr": "{climbing:toprope} voies sont équipées de moulinettes" }, "hideInAnswer": true } @@ -1465,8 +1412,7 @@ "de": "Ist hier Sportklettern möglich (feste Ankerpunkte)?", "en": "Is sport climbing possible here on fixed anchors?", "nl": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?", - "ja": "ここでは固定アンカー式のスポーツクライミングはできますか?", - "it": "È possibile arrampicarsi qua con ancoraggi fissi?" + "ja": "ここでは固定アンカー式のスポーツクライミングはできますか?" }, "mappings": [ { @@ -1476,8 +1422,7 @@ "en": "Sport climbing is possible here", "nl": "Sportklimmen/voorklimmen kan hier", "ru": "Здесь можно заняться спортивным скалолазанием", - "ja": "ここでスポーツクライミングができます", - "it": "L’arrampicata sportiva è possibile qua" + "ja": "ここでスポーツクライミングができます" } }, { @@ -1487,8 +1432,7 @@ "en": "Sport climbing is not possible here", "nl": "Sportklimmen/voorklimmen kan hier niet", "ru": "Спортивное скалолазание здесь невозможно", - "ja": "ここではスポーツクライミングはできません", - "it": "L’arrampicata sportiva non è possibile qua" + "ja": "ここではスポーツクライミングはできません" } }, { @@ -1497,8 +1441,7 @@ "de": "Hier gibt es {climbing:sport} Sportkletter-Routen", "en": "There are {climbing:sport} sport climbing routes", "nl": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes", - "ja": "スポーツクライミングの {climbing:sport} ルートがある", - "it": "Sono presenti {climbing:sport} vie di arrampicata sportiva" + "ja": "スポーツクライミングの {climbing:sport} ルートがある" }, "hideInAnswer": true } @@ -1522,8 +1465,7 @@ "de": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?", "en": "Is traditional climbing possible here (using own gear e.g. chocks)?", "nl": "Is het mogelijk om hier traditioneel te klimmen?
    (Dit is klimmen met klemblokjes en friends)", - "ja": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?", - "it": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?" + "ja": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?" }, "mappings": [ { @@ -1532,8 +1474,7 @@ "de": "Traditionelles Klettern ist hier möglich", "en": "Traditional climbing is possible here", "nl": "Traditioneel klimmen kan hier", - "ja": "ここでは伝統的な登山が可能です", - "it": "L’arrampicata tradizionale è possibile qua" + "ja": "ここでは伝統的な登山が可能です" } }, { @@ -1542,8 +1483,7 @@ "de": "Traditionelles Klettern ist hier nicht möglich", "en": "Traditional climbing is not possible here", "nl": "Traditioneel klimmen kan hier niet", - "ja": "伝統的な登山はここではできない", - "it": "L’arrampicata tradizionale non è possibile qua" + "ja": "伝統的な登山はここではできない" } }, { @@ -1552,8 +1492,7 @@ "de": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern", "en": "There are {climbing:traditional} traditional climbing routes", "nl": "Er zijn hier {climbing:traditional} traditionele klimroutes", - "ja": "{climbing:traditional} の伝統的な登山ルートがある", - "it": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale" + "ja": "{climbing:traditional} の伝統的な登山ルートがある" }, "hideInAnswer": true } @@ -1577,8 +1516,7 @@ "de": "Gibt es hier eine Speedkletter-Wand?", "en": "Is there a speed climbing wall?", "nl": "Is er een snelklimmuur (speed climbing)?", - "ja": "スピードクライミングウォールはありますか?", - "it": "È presente una prete per l’arrampicata di velocità?" + "ja": "スピードクライミングウォールはありますか?" }, "condition": { "and": [ @@ -1600,8 +1538,7 @@ "de": "Hier gibt es eine Speedkletter-Wand", "en": "There is a speed climbing wall", "nl": "Er is een snelklimmuur voor speed climbing", - "ja": "スピードクライミングウォールがある", - "it": "È presente una parete per l’arrampicata di velocità" + "ja": "スピードクライミングウォールがある" } }, { @@ -1610,8 +1547,7 @@ "de": "Hier gibt es keine Speedkletter-Wand", "en": "There is no speed climbing wall", "nl": "Er is geen snelklimmuur voor speed climbing", - "ja": "スピードクライミングウォールがない", - "it": "Non è presente una parete per l’arrampicata di velocità" + "ja": "スピードクライミングウォールがない" } }, { @@ -1620,8 +1556,7 @@ "de": "Hier gibt es {climbing:speed} Speedkletter-Routen", "en": "There are {climbing:speed} speed climbing walls", "nl": "Er zijn hier {climbing:speed} snelklimmuren", - "ja": "{climbing:speed} のスピードクライミングウォールがある", - "it": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità" + "ja": "{climbing:speed} のスピードクライミングウォールがある" }, "hideInAnswer": true } diff --git a/assets/themes/cycle_highways/cycle_highways.json b/assets/themes/cycle_highways/cycle_highways.json index 1660360f6..266312e59 100644 --- a/assets/themes/cycle_highways/cycle_highways.json +++ b/assets/themes/cycle_highways/cycle_highways.json @@ -104,8 +104,7 @@ ], "name": { "en": "cycle highways", - "de": "Radschnellwege", - "it": "strade per velocipedi" + "de": "Radschnellwege" }, "source": { "osmTags": "cycle_network=BE-VLG:cycle_highway" @@ -114,43 +113,9 @@ "title": { "render": { "en": "cycle highway", - "de": "Radschnellweg", - "it": "strada per velocipedi" + "de": "Radschnellweg" } }, - "width": { - "render": "4" - }, - "color": { - "render": "#ff7392", - "mappings": [ - { - "if": "state=", - "then": "#00acfc" - }, - { - "if": "state=temporary", - "then": "#00acfc" - } - ] - }, - "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" - } - ] - }, "filter": [ { "id": "name-alt", @@ -238,6 +203,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 b935cc480..57e56a370 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -7,8 +7,8 @@ "ja": "Cyclestreets", "zh_Hant": "單車街道", "de": "Fahrradstraßen", - "it": "Strade ciclabili", - "nb_NO": "Sykkelgater" + "nb_NO": "Sykkelgater", + "it": "Strade ciclabili" }, "shortDescription": { "nl": "Een kaart met alle gekende fietsstraten", @@ -21,7 +21,7 @@ }, "description": { "nl": "Een fietsstraat is een straat waar
    • automobilisten geen fietsers mogen inhalen
    • Er een maximumsnelheid van 30km/u geldt
    • Fietsers gemotoriseerde voertuigen links mogen inhalen
    • Fietsers nog steeds voorrang aan rechts moeten verlenen - ook aan auto's en voetgangers op het zebrapad


    Op deze open kaart kan je alle gekende fietsstraten zien en kan je ontbrekende fietsstraten aanduiden. Om de kaart aan te passen, moet je je aanmelden met OpenStreetMap en helemaal inzoomen tot straatniveau. ", - "en": "A cyclestreet is a street where motorized traffic is not allowed to overtake cyclists. They are signposted by a special traffic sign. Cyclestreets can be found in the Netherlands and Belgium, but also in Germany and France. ", + "en": "A cyclestreet is is a street where motorized traffic is not allowed to overtake cyclists. They are signposted by a special traffic sign. Cyclestreets can be found in the Netherlands and Belgium, but also in Germany and France. ", "ja": "cyclestreetとは、自動車がサイクリストを追い越すことができない道です。専用の道路標識で表示されます。Cyclestreetsはオランダやベルギーにもありますが、ドイツやフランスにもあります。 ", "zh_Hant": "單車街道是機動車輛受限制,只允許單車通行的道路。通常會有路標顯示特別的交通指標。單車街道通常在荷蘭、比利時看到,但德國與法國也有。 ", "de": "Eine Fahrradstraße ist eine Straße, auf der motorisierter Verkehr Radfahrer nicht überholen darf. Sie sind durch ein spezielles Verkehrsschild gekennzeichnet. Fahrradstraßen gibt es in den Niederlanden und Belgien, aber auch in Deutschland und Frankreich. ", @@ -57,8 +57,7 @@ "ja": "Cyclestreets", "zh_Hant": "單車街道", "it": "Strade ciclabili", - "de": "Fahrradstraßen", - "nb_NO": "Sykkelgater" + "de": "Fahrradstraßen" }, "minzoom": 7, "source": { @@ -85,11 +84,20 @@ "de": "Eine Fahrradstraße ist eine Straße, auf der motorisierter Verkehr einen Radfahrer nicht überholen darf" }, "title": "{name}", - "icon": "./assets/themes/cyclestreets/F111.svg", - "color": "#0000ff", - "width": "10", "tagRenderings": [ "images" + ], + "mapRendering": [ + { + "icon": "./assets/themes/cyclestreets/F111.svg", + "location": [ + "point" + ] + }, + { + "color": "#0000ff", + "width": "10" + } ] }, { @@ -108,11 +116,9 @@ "en": "This street will become a cyclestreet soon", "ja": "この通りはまもなくcyclestreetになります", "it": "Questa strada diventerà presto una strada ciclabile", - "de": "Diese Straße wird bald eine Fahrradstraße sein", - "nb_NO": "Denne gaten vil bli sykkelgate snart" + "de": "Diese Straße wird bald eine Fahrradstraße sein" }, "minzoom": 9, - "wayHandling": 0, "source": { "osmTags": "proposed:cyclestreet=yes" }, @@ -132,18 +138,26 @@ "en": "{name} will become a cyclestreet soon", "ja": "{name}は、もうすぐcyclestreetになる", "it": "{name} diventerà presto una strada ciclabile", - "de": "{name} wird bald eine Fahrradstraße werden", - "nb_NO": "{name} vil bli sykkelgate snart" + "de": "{name} wird bald eine Fahrradstraße werden" }, "if": "name~*" } ] }, - "icon": "./assets/themes/cyclestreets/F113.svg", - "color": "#09f9dd", - "width": "5", "tagRenderings": [ "images" + ], + "mapRendering": [ + { + "icon": "./assets/themes/cyclestreets/F113.svg", + "location": [ + "point" + ] + }, + { + "color": "#09f9dd", + "width": "5" + } ] }, { @@ -176,7 +190,6 @@ } }, "minzoom": 18, - "wayHandling": 0, "title": { "render": { "nl": "Straat", @@ -185,8 +198,7 @@ "it": "Strada", "ru": "Улица", "de": "Straße", - "eo": "Strato", - "nb_NO": "Gate" + "eo": "Strato" }, "mappings": [ { @@ -195,23 +207,32 @@ } ] }, - "icon": "./assets/svg/pencil.svg", - "width": "5", - "color": { - "render": "#aaaaaa", - "mappings": [ - { - "then": "#0000ff", - "if": "cyclestreet=yes" - }, - { - "then": "#09f9dd", - "if": "proposed:cyclestreet=yes" - } - ] - }, "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" + } ] } ], @@ -225,8 +246,7 @@ "en": "Is this street a cyclestreet?", "ja": "この通りはcyclestreetですか?", "nb_NO": "Er denne gaten en sykkelvei?", - "de": "Ist diese Straße eine Fahrradstraße?", - "it": "È una strada ciclabile?" + "de": "Ist diese Straße eine Fahrradstraße?" }, "mappings": [ { @@ -243,8 +263,7 @@ "en": "This street is a cyclestreet (and has a speed limit of 30 km/h)", "ja": "cyclestreet(最高速度は30km/h)", "nb_NO": "Denne gaten er en sykkelvei (og har en fartsgrense på 30 km/t)", - "de": "Diese Straße ist eine Fahrradstraße (mit einer Geschwindigkeitsbegrenzung von 30 km/h)", - "it": "Questa è una strada ciclabile (e ha un limite di velocità massima di 30 km/h)" + "de": "Diese Straße ist eine Fahrradstraße (mit einer Geschwindigkeitsbegrenzung von 30 km/h)" } }, { @@ -259,8 +278,7 @@ "en": "This street is a cyclestreet", "ja": "この通りはcyclestreetだ", "nb_NO": "Denne gaten er en sykkelvei", - "de": "Diese Straße ist eine Fahrradstraße", - "it": "Questa è una strada ciclabile" + "de": "Diese Straße ist eine Fahrradstraße" }, "hideInAnswer": true }, @@ -276,8 +294,7 @@ "en": "This street will become a cyclstreet soon", "ja": "この通りはまもなくcyclstreetになるだろう", "nb_NO": "Denne gaten vil bli sykkelvei ganske snart", - "de": "Diese Straße wird bald eine Fahrradstraße sein", - "it": "Diverrà tra poco una strada ciclabile" + "de": "Diese Straße wird bald eine Fahrradstraße sein" } }, { diff --git a/assets/themes/cyclofix/cyclofix.json b/assets/themes/cyclofix/cyclofix.json index e614c6f51..e6983ceeb 100644 --- a/assets/themes/cyclofix/cyclofix.json +++ b/assets/themes/cyclofix/cyclofix.json @@ -1,7 +1,7 @@ { "id": "cyclofix", "title": { - "en": "Cyclofix — an open map for cyclists", + "en": "Cyclofix - an open map for cyclists", "nl": "Cyclofix - een open kaart voor fietsers", "fr": "Cyclofix - Une carte ouverte pour les cyclistes", "gl": "Cyclofix - Un mapa aberto para os ciclistas", @@ -9,7 +9,7 @@ "ru": "Cyclofix - открытая карта для велосипедистов", "ja": "Cyclofix - サイクリストのためのオープンマップ", "zh_Hant": "單車修正 - 單車騎士的開放地圖", - "it": "Cyclofix — una mappa libera per chi va in bici", + "it": "Cyclofix - una mappa libera per chi va in bici", "nb_NO": "Cyclofix — et åpent kart for syklister" }, "description": { diff --git a/assets/themes/etymology.json b/assets/themes/etymology.json index 903dc0421..4216ff041 100644 --- a/assets/themes/etymology.json +++ b/assets/themes/etymology.json @@ -13,7 +13,7 @@ "it": "Qual è l’origine di un toponimo?" }, "description": { - "en": "On this map, you can see what an object is named after. The streets, buildings, ... come from OpenStreetMap which got linked with Wikidata. In the popup, you'll see the Wikipedia article (if it exists) or a Wikidata box of what the object is named after. If the object itself has a Wikipedia page, that'll be shown too.

    You can help contribute too!Zoom in enough and all streets will show up. You can click one and a Wikidata-search box will popup. With a few clicks, you can add an etymology link. Note that you need a free OpenStreetMap account to do this.", + "en": "On this map, you can see what an object is named after. The streets, buildings, ... come from OpenStreetMap which got linked with Wikidata. In the popup, you'll see the Wikipedia article (if it exists) or a wikidata box of what the object is named after. If the object itself has a wikipedia page, that'll be shown too.

    You can help contribute too!Zoom in enough and all streets will show up. You can click one and a Wikidata-search box will popup. With a few clicks, you can add an etymology link. Note that you need a free OpenStreetMap account to do this.", "nl": "Op deze kaart zie je waar een plaats naar is vernoemd. De straten, gebouwen, ... komen uit OpenStreetMap, waar een link naar Wikidata werd gelegd. In de popup zie je het Wikipedia-artikel van hetgeen naarwaar het vernoemd is of de Wikidata-box.

    Je kan zelf ook meehelpen!Als je ver inzoomt, krijg je alle straten te zien. Klik je een straat aan, dan krijg je een zoekfunctie waarmee je snel een nieuwe link kan leggen. Je hebt hiervoor een gratis OpenStreetMap account nodig.", "de": "Auf dieser Karte können Sie sehen, wonach ein Objekt benannt ist. Die Straßen, Gebäude, ... stammen von OpenStreetMap, das mit Wikidata verknüpft wurde. In dem Popup sehen Sie den Wikipedia-Artikel (falls vorhanden) oder ein Wikidata-Feld, nach dem das Objekt benannt ist. Wenn das Objekt selbst eine Wikipedia-Seite hat, wird auch diese angezeigt.

    Sie können auch einen Beitrag leisten!Zoomen Sie genug hinein und alle Straßen werden angezeigt. Wenn Sie auf eine Straße klicken, öffnet sich ein Wikidata-Suchfeld. Mit ein paar Klicks können Sie einen Etymologie-Link hinzufügen. Beachten Sie, dass Sie dazu ein kostenloses OpenStreetMap-Konto benötigen.", "it": "Su questa cartina sono visibili i nomi a cui sono riferiti gli oggetti. Le strade, gli edifici, etc. provengono da OpenStreetMap che è a sua volta collegata a Wikidata. Nel popup, se esiste, verrà mostrato l’articolo Wikipedia o l'elemento Wikidata a cui si riferisce il nome di quell’oggetto. Se l’oggetto stesso ha una pagina Wikpedia, anch’essa verrà mostrata.

    Anche tu puoi contribuire!Ingrandisci abbastanza e tutte le strade appariranno. Puoi cliccare su una e apparirà un popup con la ricerca Wikidata. Con pochi clic puoi aggiungere un collegamento etimologico. Tieni presente che per farlo, hai bisogno di un account gratuito su OpenStreetMap." diff --git a/assets/themes/facadegardens/facadegardens.json b/assets/themes/facadegardens/facadegardens.json index c5372d1aa..955b17ae4 100644 --- a/assets/themes/facadegardens/facadegardens.json +++ b/assets/themes/facadegardens/facadegardens.json @@ -16,15 +16,15 @@ "zh_Hant": "這地圖顯示立面花園的照片以及其他像是方向、日照以及植栽種類等實用訊息。", "it": "Questa mappa mostra i giardini verticali, con foto e informazioni utili sulla loro orientazione, sull'illuminazione solare e sui tipi di piante.", "fr": "Cette carte indique les murs végétalisés avec des photos et des informations comme leur orientation, l’ensoleillement et le type de plantes.", - "de": "Diese Karte zeigt Fassadengärten mit Bildern und nützlichen Informationen über Ausrichtung, Sonneneinstrahlung und Pflanzenarten." + "de": "Diese Karte zeigt Fassadengärten mit Bildern und Details zu Ausrichtung, Sonneneinstrahlung und Pflanzen." }, "description": { "nl": "Ontharde voortuintjes, groene gevels en bomen ín de stad brengen naast rust ook een mooiere stad, een grotere biodiversiteit, een verkoelend effect en een betere luchtkwaliteit.
    Klimaan VZW en 'Mechelen Klimaatneutraal' willen met het project Klim(t)aan je Gevel bestaande en nieuwe geveltuintjes in kaart brengen als voorbeeld voor mensen zelf een tuintje willen aanleggen of voor stadwandelaars die houden van de natuur.
    Meer info over het project op klimaan.be.", "en": "Facade gardens, green facades and trees in the city not only bring peace and quiet, but also a more beautiful city, greater biodiversity, a cooling effect and better air quality.
    Klimaan VZW and Mechelen Klimaatneutraal want to map existing and new facade gardens as an example for people who want to build their own garden or for city walkers who love nature.
    More info about the project at klimaan.be.", "ja": "ファサード庭園、都市の緑のファサードと樹木は、平和と静けさをもたらすだけでなく、より美しい都市、より大きな生物多様性、冷却効果、より良い大気質をもたらす。
    KlimaanのVZWとMechelenのKlimaatneutraalは、自分で庭を作りたい人や自然を愛する都市の歩行者のために、既存のファサード庭園と新しいファサード庭園のマッピングしたいと考えています。
    このプロジェクトに関する詳細情報はklimaanにあります。", "fr": "Les jardins muraux en ville n’apportent pas seulement paix et tranquillité mais contribuent à embellir la ville, favoriser la biodiversité, régule la température et assainit l’air.
    Klimaan VZW et Mechelen Klimaatneutraal veulent cartographier les jardins muraux comme exemple pour les personnes souhaitant en construire ainsi que celles aimant la nature.
    Plus d’infos sur klimaan.be.", - "de": "Fassadengärten, grüne Fassaden und Bäume in der Stadt bringen nicht nur Ruhe und Frieden, sondern auch eine schönere Stadt, eine größere Artenvielfalt, einen Kühleffekt und eine bessere Luftqualität.
    Klimaan VZW und Mechelen Klimaatneutraal wollen bestehende und neue Fassadengärten als Beispiel für Menschen, die ihren eigenen Garten anlegen wollen, oder für naturverbundene Stadtspaziergänger kartieren.
    Mehr Informationen über das Projekt unter klimaan.be.", - "it": "I giardini veritcali e gli alberi in città non solo portano pace e tranquillità ma creano anche un ambiente più bello, aumentano la biodiversità, rendono il clima più fresco e migliorano la qualità dell’aria.
    Klimaan VZW e Mechelen Klimaatneutraal vogliono mappare sia i giardini verticali esistenti che quelli nuovi per mostrarli a quanti vogliono costruire un loro proprio giardino o per quelli che amano la natura e vogliono camminare per la città.
    Per ulteriori informazioni visita klimaan.be." + "it": "I giardini veritcali e gli alberi in città non solo portano pace e tranquillità ma creano anche un ambiente più bello, aumentano la biodiversità, rendono il clima più fresco e migliorano la qualità dell’aria.
    Klimaan VZW e Mechelen Klimaatneutraal vogliono mappare sia i giardini verticali esistenti che quelli nuovi per mostrarli a quanti vogliono costruire un loro proprio giardino o per quelli che amano la natura e vogliono camminare per la città.
    Per ulteriori informazioni visita klimaan.be.", + "de": "Fassadengärten, grüne Fassaden und Bäume in der Stadt bringen nicht nur Ruhe und Frieden, sondern auch eine schönere Stadt, eine größere Artenvielfalt, einen Kühleffekt und eine bessere Luftqualität.
    Klimaan VZW und Mechelen Klimaatneutraal wollen bestehende und neue Fassadengärten als Beispiel für Menschen, die ihren eigenen Garten anlegen wollen, oder für naturverbundene Stadtspaziergänger kartieren.
    Mehr Informationen über das Projekt unter klimaan.be." }, "language": [ "nl", @@ -55,8 +55,7 @@ "ja": "ファサード庭園", "zh_Hant": "立面花園", "fr": "Jardins muraux", - "de": "Fassadengärten", - "it": "Giardini verticali" + "de": "Fassadengärten" }, "minzoom": 12, "source": { @@ -74,8 +73,7 @@ "ja": "ファサード庭園", "zh_Hant": "立面花園", "fr": "Jardin mural", - "de": "Fassadengarten", - "it": "Giardino verticale" + "de": "Fassadengarten" } }, "description": { @@ -84,59 +82,24 @@ "ja": "ファサード庭園", "zh_Hant": "立面花園", "fr": "Jardins muraux", - "de": "Fassadengärten", - "it": "Giardini verticali" + "de": "Fassadengärten" }, - "iconOverlays": [ - { - "if": "plant~.*vine.*", - "then": "circle:white;./assets/themes/facadegardens/klimplant.svg", - "badge": true - }, - { - "if": "plant~.*groundcover.*", - "then": "circle:white;./assets/themes/facadegardens/bodembedekker.svg", - "badge": true - }, - { - "if": "edible=true", - "then": "circle:white;./assets/themes/facadegardens/eetbaar.svg", - "badge": true - }, - { - "if": "rain_barel=yes", - "then": "circle:white;./assets/themes/facadegardens/gevelton.svg", - "badge": true - }, - { - "if": "plant~.*shrub.*", - "then": "circle:white;./assets/themes/facadegardens/struik.svg", - "badge": true - }, - { - "if": "plant~.*flower.*", - "then": "circle:white;./assets/themes/facadegardens/bloei.svg", - "badge": true - } - ], "tagRenderings": [ "images", { "render": { "nl": "Oriëntatie: {direction} (waarbij 0=N en 90=O)", - "en": "Orientation: {direction} (where 0=N and 90=E)", + "en": "Orientation: {direction} (where 0=N and 90=O)", "ja": "方向: {direction} (0=N で 90=O)", "fr": "Orientation : {direction} (0 pour le Nord et 90 pour l’Ouest)", - "de": "Ausrichtung: {direction} (wobei 0=N und 90=O)", - "it": "Orientamento: {direction} (0 per il Nord e 90 per l’Est)" + "de": "Ausrichtung: {direction} (wobei 0=N und 90=O)" }, "question": { "nl": "Hoe is de tuin georiënteerd?", "en": "What is the orientation of the garden?", "ja": "庭の向きはどうなっていますか?", "fr": "Quelle est l’orientation du jardin ?", - "de": "Wie ist der Garten ausgerichtet?", - "it": "Com’è orientato questo giardino?" + "de": "Wie ist der Garten ausgerichtet?" }, "freeform": { "type": "direction", @@ -211,8 +174,7 @@ "en": "Is there a water barrel installed for the garden?", "ja": "庭に水桶が設置されているのですか?", "fr": "Des réserves d’eau ont-elles été installées pour le jardin ?", - "de": "Gibt es ein Wasserfass für den Garten?", - "it": "È stata installata una riserva d’acqua per il giardino?" + "de": "Gibt es ein Wasserfass für den Garten?" }, "mappings": [ { @@ -402,44 +364,6 @@ "id": "facadegardens-description" } ], - "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" - } - ] - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "50,50,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -464,7 +388,71 @@ } } ], - "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 2b5a81f53..9dc192b8a 100644 --- a/assets/themes/ghostbikes/ghostbikes.json +++ b/assets/themes/ghostbikes/ghostbikes.json @@ -43,7 +43,7 @@ "description": { "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.

    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.", "nl": "Een Witte Fiets of Spookfiets is een aandenken aan een fietser die bij een verkeersongeval om het leven kwam. Het gaat om een fiets die volledig wit is geschilderd en in de buurt van het ongeval werd geinstalleerd.

    Op deze kaart zie je alle witte fietsen die door OpenStreetMap gekend zijn. Ontbreekt er een Witte Fiets of wens je informatie aan te passen? Meld je dan aan met een (gratis) OpenStreetMap account.", - "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 ist.

    Auf dieser Karte kann man alle Geisterräder sehen, die in OpenStreetMap eingetragen sind. Fehlt ein Geisterrad? Jeder kann hier Informationen hinzufügen oder aktualisieren - Sie benötigen lediglich einen (kostenlosen) OpenStreetMap-Account.", + "de": "Ein Geisterrad ist ein weißes Fahrrad, dass zum Gedenken eines tödlich verunglückten Radfahrers vor Ort aufgestellt wurde.

    Auf dieser Karte kann man alle Geisterräder sehen, die OpenStreetMap eingetragen sind. Fehlt ein Geisterrad? Jeder kann hier Informationen hinzufügen oder aktualisieren - Sie benötigen lediglich einen (kostenlosen) OpenStreetMap-Account.", "ja": "ゴーストバイクは、交通事故で死亡したサイクリストを記念するもので、事故現場の近くに恒久的に置かれた白い自転車の形をしています。

    このマップには、OpenStreetMapで知られているゴーストバイクがすべて表示されます。ゴーストバイクは行方不明ですか?誰でもここで情報の追加や更新ができます。必要なのは(無料の)OpenStreetMapアカウントだけです。", "zh_Hant": "幽靈單車是用來紀念死於交通事故的單車騎士,在事發地點附近放置白色單車。

    在這份地圖上面,你可以看到所有在開放街圖已知的幽靈單車。有缺漏的幽靈單車嗎?所有人都可以在這邊新增或是更新資訊-只有你有(免費)開放街圖帳號。", "fr": "Les vélos fantômes sont des mémoriaux pour les cyclistes tuées sur la route, prenant la forme de vélos blancs placés à proximité des faits.

    Cette carte indique leur emplacement à partir d’OpenStreetMap. Il est possible de contribuer aux informations ici, sous réserve d’avoir un compte OpenStreetMap (gratuit).", @@ -57,5 +57,8 @@ "layers": [ "ghost_bike" ], - "defaultBackgroundId": "CartoDB.Positron" + "defaultBackgroundId": "CartoDB.Positron", + "clustering": { + "maxZoom": 0 + } } \ No newline at end of file diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index 4f0d99d9d..2d487ab3d 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -7,10 +7,12 @@ "nl": "Grb Fixup" }, "description": { - "nl": "GRB Fixup" + "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" + "nl", + "en" ], "maintainer": "", "icon": "./assets/svg/bug.svg", @@ -20,13 +22,186 @@ "startZoom": 14, "widenFactor": 2, "socialImage": "", + "clustering": { + "maxZoom": 15 + }, + "overrideAll": { + "minzoom": 18 + }, + "trackAllNodes": true, + "enableGeolocation": false, "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" + ], + "mapRendering": [ + { + "icon": "square:#00f", + "iconSize": "5,5,center", + "location": "point" + } + ] + } + }, + { + "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}", + "question": { + "en": "What kind of building is this?" + }, + "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" }, - "minzoom": 12, "source": { "maxCacheAge": 0, "osmTags": { @@ -178,26 +353,34 @@ } } ], - "label": { - "mappings": [ - { - "if": "addr:housenumber~*", - "then": "
    {addr:housenumber}
    " + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "label": { + "mappings": [ + { + "if": "addr:housenumber~*", + "then": "
    {addr:housenumber}
    " + } + ] + }, + "iconSize": { + "render": "40,40,center" } - ] - }, - "width": { - "render": "2" - }, - "iconSize": { - "render": "40,40,center" - }, - "dashes": "2 2", - "color": { - "render": "#00f" - }, - "wayHandling": 2, - "presets": [] + }, + { + "dashes": "2 2", + "color": { + "render": "#00f" + }, + "width": { + "render": "2" + } + } + ] }, { "id": "crab-addresses 2021-10-26", @@ -208,22 +391,252 @@ "geoJsonZoomLevel": 18, "maxCacheAge": 0 }, - "minzoom": 19, "name": "CRAB-addressen", "title": "CRAB-adres", - "icon": "circle:#bb3322", - "iconSize": "15,15,center", + "mapRendering": [ + { + "location": [ + "point" + ], + "icon": "circle:#bb3322", + "iconSize": "15,15,center" + } + ], + "calculatedTags": [ + "_embedded_in=feat.overlapWith('OSM-buildings')[0]?.feat?.properties ", + "_embedding_nr=feat.get('_embedded_in')['addr:housenumber']", + "_embedding_street=feat.get('_embedded_in')['addr:street']", + "_embedding_id=feat.get('_embedded_in').id" + ], + "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(addr:street=$STRAATNM; addr:housenumber=$HUISNR)}" + "render": "{import_button(OSM-buildings, addr:street=$STRAATNM; addr:housenumber=$HUISNR,Import this address,,,OSM-buildings,5)}" + }, + { + "id": "apply-button", + "render": "{tag_apply(addr:street=$STRAATNM; addr:housenumber=$HUISNR,Apply this address on the OSM-building,,_embedding_id)}", + "condition": "_embedded_in!=" + } + ] + }, + { + "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, @@ -231,12 +644,98 @@ }, "name": "GRB geometries", "title": "GRB outline", - "minzoom": 19, + "calculatedTags": [ + "_overlaps_with=feat.overlapWith('OSM-buildings').filter(f => f.overlap > 1 && (feat.get('_surface') < 20 || f.overlap / feat.get('_surface')) > 0.5)[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", + "_target_building_type=feat.properties['_osm_obj:building'] === 'yes' ? feat.properties.building : (feat.properties['_osm_obj:building'] ?? feat.properties.building)" + ], "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=$_target_building_type; 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" + "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 9e0722aa9..c301c7b23 100644 --- a/assets/themes/hackerspaces/hackerspaces.json +++ b/assets/themes/hackerspaces/hackerspaces.json @@ -34,15 +34,13 @@ "id": "hackerspaces", "name": { "en": "Hackerspace", - "de": "Hackerspace", - "it": "Hackerspace" + "de": "Hackerspace" }, "minzoom": 8, "title": { "render": { "en": "Hackerspace", - "de": "Hackerspace", - "it": "Hackerspace" + "de": "Hackerspace" }, "mappings": [ { @@ -53,41 +51,35 @@ }, "then": { "en": " {name}", - "de": " {name}", - "eo": " {name}", - "it": " {name}" + "de": " {name}" } } ] }, "description": { "en": "Hackerspace", - "de": "Hackerspace", - "it": "Hackerspace" + "de": "Hackerspace" }, "tagRenderings": [ { "id": "is_makerspace", "question": { "en": "Is this a hackerspace or a makerspace?", - "de": "Ist dies ein Hackerspace oder ein Makerspace?", - "it": "È un hackerspace o un makerspace?" + "de": "Ist dies ein Hackerspace oder ein Makerspace?" }, "mappings": [ { "if": "hackerspace=makerspace", "then": { "en": "This is a makerspace", - "de": "Dies ist ein Makerspace", - "it": "Si tratta di un makerspace" + "de": "Dies ist ein Makerspace" } }, { "if": "hackerspace=", "then": { "en": "This is a traditional (software oriented) hackerspace", - "de": "Dies ist ein traditioneller (softwareorientierter) Hackerspace", - "it": "Si tratta di un hackerspace tradizionale (orientato al software)" + "de": "Dies ist ein traditioneller (softwareorientierter) Hackerspace" } } ] @@ -95,13 +87,11 @@ { "question": { "en": "What is the name of this hackerspace?", - "de": "Wie lautet der Name dieses Hackerspace?", - "it": "Qual è il nome di questo hackerspace?" + "de": "Wie lautet der Name dieses Hackerspace?" }, "render": { "en": "This hackerspace is named {name}", - "de": "Dieser Hackerspace heißt {name}", - "it": "Questo hackerspace si chiama {name}" + "de": "Dieser Hackerspace heißt {name}" }, "freeform": { "key": "name" @@ -114,8 +104,7 @@ { "question": { "en": "When is this hackerspace opened?", - "de": "Wann hat dieser Hackerspace geöffnet?", - "it": "Quando è aperto questo hackerspace?" + "de": "Wann hat dieser Hackerspace geöffnet?" }, "freeform": { "key": "opening_hours", @@ -123,9 +112,7 @@ }, "render": { "en": "{opening_hours_table()}", - "de": "{opening_hours_table()}", - "eo": "{opening_hours_table()}", - "it": "{opening_hours_table()}" + "de": "{opening_hours_table()}" }, "mappings": [ { @@ -136,8 +123,7 @@ }, "then": { "en": "Opened 24/7", - "de": "durchgehend geöffnet", - "it": "Aperto sempre" + "de": "durchgehend geöffnet" } } ], @@ -148,8 +134,7 @@ "id": "hs-club-mate", "question": { "en": "Does this hackerspace serve Club Mate?", - "de": "Gibt es in diesem Hackerspace Club Mate?", - "it": "In questo hackerspace si serve Club-Mate?" + "de": "Gibt es in diesem Hackerspace Club Mate?" }, "mappings": [ { @@ -160,8 +145,7 @@ }, "then": { "en": "This hackerspace serves club mate", - "de": "In diesem Hackerspace gibt es Club Mate", - "it": "In questo hackerspace viene servito Club-Mate" + "de": "In diesem Hackerspace gibt es Club Mate" } }, { @@ -172,8 +156,7 @@ }, "then": { "en": "This hackerspace does not serve club mate", - "de": "In diesem Hackerspace gibt es kein Club Mate", - "it": "In questo hackerspace non viene servito Club-Mate" + "de": "In diesem Hackerspace gibt es kein Club Mate" } } ] @@ -181,13 +164,11 @@ { "render": { "en": "This hackerspace was founded at {start_date}", - "de": "Dieser Hackerspace wurde gegründet am {start_date}", - "it": "Questo hackerspace è stato creato il {start_date}" + "de": "Dieser Hackerspace wurde gegründet am {start_date}" }, "question": { "en": "When was this hackerspace founded?", - "de": "Wann wurde dieser Hackerspace gegründet?", - "it": "Quando è stato creato questo hackerspace?" + "de": "Wann wurde dieser Hackerspace gegründet?" }, "freeform": { "key": "start_date", @@ -196,33 +177,6 @@ "id": "hackerspaces-start_date" } ], - "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", - "eo": "./assets/themes/hackerspaces/led.png", - "it": "./assets/themes/hackerspaces/led.png" - } - } - ] - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -230,13 +184,11 @@ ], "title": { "en": "Hackerspace", - "de": "Hackerspace", - "it": "Hackerspace" + "de": "Hackerspace" }, "description": { "en": "A hackerspace is an area where people interested in software gather", - "de": "Ein Hackerspace ist ein Ort, an dem sich Menschen treffen, die sich für Software interessieren", - "it": "Un hackerspace è un’area dove si ritrovano le persone interessate al software" + "de": "Ein Hackerspace ist ein Ort, an dem sich Menschen treffen, die sich für Software interessieren" } }, { @@ -246,24 +198,53 @@ ], "title": { "en": "Makerspace", - "de": "Makerspace", - "it": "Makerspace" + "de": "Makerspace" }, "description": { "en": "A makerspace is a place where DIY-enthusiasts gather to experiment with electronics such as arduino, LEDstrips, ...", - "de": "Ein Makerspace ist ein Ort, an dem Heimwerker-Enthusiasten zusammenkommen, um mit Elektronik zu experimentieren, wie Arduino, LED-Strips, ...", - "it": "Un makerspace è un luogo dove gli amanti del fai-da-te si ritrovano per sperimentare con dispositivi di elettronica come arduino, strisce LED, etc." + "de": "Ein Makerspace ist ein Ort, an dem Heimwerker-Enthusiasten zusammenkommen, um mit Elektronik zu experimentieren, wie Arduino, LED-Strips, ..." } } ], - "wayHandling": 2, "source": { "osmTags": { "and": [ "leisure=hackerspace" ] } - } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hackerspaces/glider.svg", + "mappings": [ + { + "if": { + "and": [ + "hackerspace=makerspace" + ] + }, + "then": "./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 1e5cbf5c3..4fd07734b 100644 --- a/assets/themes/hailhydrant/hailhydrant.json +++ b/assets/themes/hailhydrant/hailhydrant.json @@ -6,8 +6,8 @@ "zh_Hant": "消防栓、滅火器、消防隊、以及急救站。", "ru": "Пожарные гидранты, огнетушители, пожарные станции и станции скорой помощи.", "fr": "Bornes incendies, extincteurs, casernes de pompiers et ambulanciers.", - "it": "Idranti, estintori, caserme dei vigili del fuoco e stazioni delle ambulanze.", - "nb_NO": "Hydranter, brannslukkere, brannstasjoner, og ambulansestasjoner." + "nb_NO": "Hydranter, brannslukkere, brannstasjoner, og ambulansestasjoner.", + "it": "Idranti, estintori, caserme dei vigili del fuoco e stazioni delle ambulanze." }, "shortDescription": { "en": "Map to show hydrants, extinguishers, fire stations, and ambulance stations.", @@ -15,8 +15,8 @@ "zh_Hant": "顯示消防栓、滅火器、消防隊與急救站的地圖。", "ru": "Карта пожарных гидрантов, огнетушителей, пожарных станций и станций скорой помощи.", "fr": "Carte indiquant les bornes incendies, extincteurs, casernes de pompiers et ambulanciers.", - "de": "Hydranten, Feuerlöscher, Feuerwachen und Rettungswachen.", - "it": "Carta che mostra gli idranti, gli estintori, le caserme dei vigili del fuoco e le stazioni delle ambulanze." + "it": "Carta che mostra gli idranti, gli estintori, le caserme dei vigili del fuoco e le stazioni delle ambulanze.", + "de": "Hydranten, Feuerlöscher, Feuerwachen und Rettungswachen." }, "description": { "en": "On this map you can find and update hydrants, fire stations, ambulance stations, and extinguishers in your favorite neighborhoods.\n\nYou can track your precise location (mobile only) and select layers that are relevant for you in the bottom left corner. You can also use this tool to add or edit pins (points of interest) to the map and provide additional details by answering available questions.\n\nAll changes you make will automatically be saved in the global database of OpenStreetMap and can be freely re-used by others.", @@ -56,8 +56,7 @@ "nb_NO": "Kart over brannhydranter", "ru": "Карта пожарных гидрантов", "fr": "Carte des bornes incendie", - "de": "Karte der Hydranten", - "it": "Mappa degli idranti" + "de": "Karte der Hydranten" }, "minzoom": 14, "source": { @@ -74,8 +73,7 @@ "ja": "消火栓", "nb_NO": "Brannhydrant", "fr": "Bornes incendie", - "de": "Hydrant", - "it": "Idrante" + "de": "Hydrant" } }, "description": { @@ -85,8 +83,7 @@ "nb_NO": "Kartlag for å vise brannhydranter.", "ru": "Слой карты, отображающий пожарные гидранты.", "fr": "Couche des bornes incendie.", - "de": "Kartenebene zur Anzeige von Hydranten.", - "it": "Livello della mappa che mostra gli idranti antincendio." + "de": "Kartenebene zur Anzeige von Hydranten." }, "tagRenderings": [ { @@ -97,8 +94,7 @@ "nb_NO": "Hvilken farge har brannhydranten?", "ru": "Какого цвета гидрант?", "fr": "Quelle est la couleur de la borne ?", - "de": "Welche Farbe hat der Hydrant?", - "it": "Qual è il colore dell’idrante?" + "de": "Welche Farbe hat der Hydrant?" }, "render": { "en": "The hydrant color is {colour}", @@ -106,8 +102,7 @@ "nb_NO": "Brannhydranter er {colour}", "ru": "Цвет гидранта {colour}", "fr": "La borne est {colour}", - "de": "Der Hydrant hat die Farbe {colour}", - "it": "Il colore dell’idrante è {colour}" + "de": "Der Hydrant hat die Farbe {colour}" }, "freeform": { "key": "colour" @@ -124,8 +119,7 @@ "ja": "消火栓の色は不明です。", "ru": "Цвет гидранта не определён.", "fr": "La borne est de couleur inconnue.", - "de": "Die Farbe des Hydranten ist unbekannt.", - "it": "Il colore dell’idrante è sconosciuto." + "de": "Die Farbe des Hydranten ist unbekannt." }, "hideInAnswer": true }, @@ -140,8 +134,7 @@ "ja": "消火栓の色は黄色です。", "ru": "Гидрант жёлтого цвета.", "fr": "La borne est jaune.", - "de": "Die Farbe des Hydranten ist gelb.", - "it": "Il colore dell’idrante è giallo." + "de": "Die Farbe des Hydranten ist gelb." } }, { @@ -209,8 +202,7 @@ "en": " Pillar type.", "ja": " ピラー型。", "fr": " Pilier.", - "de": " Säulenart.", - "it": " Soprasuolo." + "de": " Säulenart." } }, { @@ -223,8 +215,7 @@ "en": " Pipe type.", "ja": " パイプ型。", "fr": " Tuyau.", - "de": " Rohrtyp.", - "it": " Tubo." + "de": " Rohrtyp." } }, { @@ -239,8 +230,7 @@ "ru": " Тип стены.", "ja": " 壁型。", "fr": " Mural.", - "de": " Wandtyp.", - "it": " A muro." + "de": " Wandtyp." } }, { @@ -253,8 +243,7 @@ "en": " Underground type.", "ja": "地下式。", "fr": " Enterré.", - "de": " Untergrundtyp.", - "it": " Sottosuolo." + "de": " Untergrundtyp." } } ] @@ -265,15 +254,13 @@ "en": "Update the lifecycle status of the hydrant.", "ja": "消火栓のライフサイクルステータスを更新します。", "fr": "Mettre à jour l’état de la borne.", - "de": "Aktualisieren Sie den Lebenszyklusstatus des Hydranten.", - "it": "Aggiorna lo stato di funzionamento dell’idrante." + "de": "Aktualisieren Sie den Lebenszyklusstatus des Hydranten." }, "render": { "en": "Lifecycle status", "ja": "ライフサイクルステータス", "fr": "État", - "de": "Lebenszyklus-Status", - "it": "Stato di funzionamento" + "de": "Lebenszyklus-Status" }, "freeform": { "key": "disused:emergency" @@ -290,8 +277,7 @@ "ja": "消火栓は(完全にまたは部分的に)機能しています。", "ru": "Гидрант (полностью или частично) в рабочем состоянии.", "fr": "La borne est en état, ou partiellement en état, de fonctionner.", - "de": "Der Hydrant ist (ganz oder teilweise) in Betrieb.", - "it": "L’idrante è (parzialmente o completamente) funzionante." + "de": "Der Hydrant ist (ganz oder teilweise) in Betrieb." } }, { @@ -305,8 +291,7 @@ "en": "The hydrant is unavailable.", "ja": "消火栓は使用できません。", "fr": "La borne est hors-service.", - "de": "Der Hydrant ist nicht verfügbar.", - "it": "L’idrante è fuori servizio." + "de": "Der Hydrant ist nicht verfügbar." } }, { @@ -321,26 +306,13 @@ "ja": "消火栓が撤去されました。", "ru": "Гидрант демонтирован.", "fr": "La borne a été retirée.", - "de": "Der Hydrant wurde entfernt.", - "it": "L’idrante è stato rimosso." + "de": "Der Hydrant wurde entfernt." } } ] }, "images" ], - "icon": { - "render": "./assets/themes/hailhydrant/hydrant.svg" - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "20,20,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -352,19 +324,38 @@ "ja": "消火栓", "nb_NO": "Brannhydrant", "fr": "Borne incendie", - "de": "Löschwasser-Hydrant", - "it": "Idrante antincendio" + "de": "Löschwasser-Hydrant" }, "description": { "en": "A hydrant is a connection point where firefighters can tap water. It might be located underground.", "ja": "消火栓は消防士が水を汲み上げることができる接続点です。地下にあるかもしれません。", "fr": "Une borne incendie est un point où les pompiers peuvent s’alimenter en eau. Elle peut être enterrée.", - "de": "Ein Hydrant ist ein Anschlusspunkt, an dem die Feuerwehr Wasser zapfen kann. Er kann sich unterirdisch befinden.", - "it": "Un idrante è un punto di collegamento dove i pompieri possono estrarre acqua. Potrebbe trovarsi sottoterra." + "de": "Ein Hydrant ist ein Anschlusspunkt, an dem die Feuerwehr Wasser zapfen kann. Er kann sich unterirdisch befinden." } } ], - "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", @@ -374,8 +365,7 @@ "nb_NO": "Kart over brannhydranter", "ru": "Карта огнетушителей.", "fr": "Couche des extincteurs.", - "de": "Karte mit Feuerlöschern.", - "it": "Cartina degli estintori." + "de": "Karte mit Feuerlöschern." }, "minzoom": 14, "source": { @@ -392,8 +382,7 @@ "ja": "消火器", "nb_NO": "Brannslokkere", "fr": "Exctincteurs", - "de": "Feuerlöscher", - "it": "Estintori" + "de": "Feuerlöscher" } }, "description": { @@ -403,8 +392,7 @@ "nb_NO": "Kartlag for å vise brannslokkere.", "ru": "Слой карты, отображающий огнетушители.", "fr": "Couche des lances à incendie.", - "de": "Kartenebene zur Anzeige von Hydranten.", - "it": "Livello della mappa che mostra gli idranti antincendio." + "de": "Kartenebene zur Anzeige von Hydranten." }, "tagRenderings": [ { @@ -414,17 +402,14 @@ "ja": "場所:{location}", "ru": "Местоположение: {location}", "fr": "Emplacement : {location}", - "de": "Standort: {location}", - "eo": "Loko: {location}", - "it": "Posizione: {location}" + "de": "Standort: {location}" }, "question": { "en": "Where is it positioned?", "ja": "どこにあるんですか?", "ru": "Где это расположено?", "fr": "Où est-elle positionnée ?", - "de": "Wo befindet er sich?", - "it": "Dove è posizionato?" + "de": "Wo befindet er sich?" }, "mappings": [ { @@ -438,8 +423,7 @@ "ja": "屋内にある。", "ru": "Внутри.", "fr": "Intérieur.", - "de": "Im Innenraum vorhanden.", - "it": "Si trova all’interno." + "de": "Im Innenraum vorhanden." } }, { @@ -453,8 +437,7 @@ "ja": "屋外にある。", "ru": "Снаружи.", "fr": "Extérieur.", - "de": "Im Außenraum vorhanden.", - "it": "Si trova all’esterno." + "de": "Im Außenraum vorhanden." } } ], @@ -464,18 +447,6 @@ }, "images" ], - "icon": { - "render": "./assets/themes/hailhydrant/Twemoji12_1f9ef.svg" - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "20,20,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -487,20 +458,30 @@ "nb_NO": "Brannslukker", "ru": "Огнетушитель", "fr": "Extincteur", - "de": "Feuerlöscher", - "it": "Estintore" + "de": "Feuerlöscher" }, "description": { "en": "A fire extinguisher is a small, portable device used to stop a fire", "ja": "消火器は、火災を止めるために使用される小型で携帯可能な装置である", "ru": "Огнетушитель - небольшое переносное устройство для тушения огня", "fr": "Un extincteur est un appareil portatif servant à éteindre un feu", - "de": "Ein Feuerlöscher ist ein kleines, tragbares Gerät, das dazu dient, ein Feuer zu löschen", - "it": "Un estintore è un dispositivo portatile di piccole dimensioni usato per spegnere un incendio" + "de": "Ein Feuerlöscher ist ein kleines, tragbares Gerät, das dazu dient, ein Feuer zu löschen" } } ], - "wayHandling": 1 + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/hailhydrant/Twemoji12_1f9ef.svg" + }, + "iconSize": { + "render": "20,20,center" + }, + "location": [ + "point" + ] + } + ] }, { "id": "fire_stations", @@ -521,7 +502,6 @@ ] } }, - "wayHandling": 2, "title": { "render": { "en": "Fire Station", @@ -560,8 +540,7 @@ "ja": "このステーションの名前は{name}です。", "it": "Questa caserma si chiama {name}.", "ru": "Эта часть называется {name}.", - "fr": "Cette station s’appelle {name}.", - "nb_NO": "Denne stasjonen heter {name}." + "fr": "Cette station s’appelle {name}." } }, { @@ -580,8 +559,7 @@ "en": "This station is along a highway called {addr:street}.", "ja": "{addr:street} 沿いにあります。", "ru": "Часть расположена вдоль шоссе {addr:street}.", - "fr": "La station fait partie de la {addr:street}.", - "it": "La stazione si trova in una strada chiamata {addr:street}." + "fr": "La station fait partie de la {addr:street}." } }, { @@ -590,8 +568,7 @@ "en": "Where is the station located? (e.g. name of neighborhood, villlage, or town)", "ja": "このステーションの住所は?(例: 地区、村、または町の名称)", "ru": "Где расположена часть? (напр., название населённого пункта)", - "fr": "Dans quelle localité la station est-elle située ?", - "it": "In che località si trova la stazione? (ad es. quartiere, paese o città)" + "fr": "Dans quelle localité la station est-elle située ?" }, "freeform": { "key": "addr:place" @@ -600,8 +577,7 @@ "en": "This station is found within {addr:place}.", "ja": "このステーションは{addr:place}にあります。", "ru": "Эта часть расположена в {addr:place}.", - "fr": "La station fait partie de {addr:place}.", - "it": "La stazione si trova a {addr:place}." + "fr": "La station fait partie de {addr:place}." } }, { @@ -609,14 +585,12 @@ "question": { "en": "What agency operates this station?", "ja": "このステーションを運営しているのはどこですか?", - "fr": "Quel est l’exploitant de la station ?", - "it": "Quale agenzia gestisce questa stazione?" + "fr": "Quel est l’exploitant de la station ?" }, "render": { "en": "This station is operated by {operator}.", "ja": "このステーションは{operator}によって運営されています。", - "fr": "Cette station est opérée par {operator}.", - "it": "Questa stazione è gestita da {operator}." + "fr": "Cette station est opérée par {operator}." }, "freeform": { "key": "operator" @@ -632,9 +606,7 @@ "then": { "en": "Bureau of Fire Protection", "ja": "消防局(消防庁)", - "fr": "Brigade de Protection du Feu", - "de": "Brandschutzbehörde", - "it": "Servizio antincendio governativo" + "fr": "Brigade de Protection du Feu" } } ] @@ -644,14 +616,12 @@ "question": { "en": "How is the station operator classified?", "ja": "ステーションの運営の分類は?", - "fr": "Quel est le type d’exploitant ?", - "it": "Com’è classificato il gestore di questa stazione?" + "fr": "Quel est le type d’exploitant ?" }, "render": { "en": "The operator is a(n) {operator:type} entity.", "ja": "運営者は、{operator:type} です。", - "fr": "L’exploitant est de type {operator:type}.", - "it": "Il gestore è un ente {operator:type}." + "fr": "L’exploitant est de type {operator:type}." }, "freeform": { "key": "operator:type" @@ -666,9 +636,7 @@ "then": { "en": "The station is operated by the government.", "ja": "ステーションは自治体が運営する。", - "fr": "La station est opérée par le gouvernement.", - "it": "Questa stazione è gestita dal governo.", - "nb_NO": "Stasjonen drives av myndighetene." + "fr": "La station est opérée par le gouvernement." } }, { @@ -680,8 +648,7 @@ "then": { "en": "The station is operated by a community-based, or informal organization.", "ja": "任意団体やコミュニティが運営しているステーションである。", - "fr": "La station est opérée par une organisation informelle.", - "it": "Questa stazione è gestita dalla comunità oppure un’associazione informale." + "fr": "La station est opérée par une organisation informelle." } }, { @@ -693,8 +660,7 @@ "then": { "en": "The station is operated by a formal group of volunteers.", "ja": "公益団体が運営しているステーションである。", - "fr": "La station est opérée par un groupe officiel de bénévoles.", - "it": "Questa stazione è gestita da un gruppo di volontari ufficiale." + "fr": "La station est opérée par un groupe officiel de bénévoles." } }, { @@ -706,26 +672,13 @@ "then": { "en": "The station is privately operated.", "ja": "個人が運営しているステーションである。", - "fr": "La station est opérée par un groupe privé.", - "it": "Questa stazione è gestita da privati." + "fr": "La station est opérée par un groupe privé." } } ] }, "images" ], - "icon": { - "render": "./assets/themes/hailhydrant/Twemoji12_1f692.svg" - }, - "width": { - "render": "1" - }, - "iconSize": { - "render": "35,35,center" - }, - "color": { - "render": "#c22" - }, "presets": [ { "tags": [ @@ -736,16 +689,35 @@ "ja": "消防署", "ru": "Пожарная часть", "fr": "Caserne de pompiers", - "de": "Feuerwache", - "it": "Caserma dei vigili del fuoco", - "nb_NO": "Brannstasjon" + "de": "Feuerwache" }, "description": { "en": "A fire station is a place where the fire trucks and firefighters are located when not in operation.", "ja": "消防署は、運転していないときに消防車や消防士がいる場所です。", "fr": "Une caserne de pompiers est un lieu où les pompiers et leur équipements sont situés en dehors des missions.", - "de": "Eine Feuerwache ist ein Ort, an dem die Feuerwehrfahrzeuge und die Feuerwehrleute untergebracht sind, wenn sie nicht im Einsatz sind.", - "it": "Una caserma dei pompieri è un luogo dove si trovano i mezzi antincendio e i pompieri tra una missione e l’altra." + "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" } } ] @@ -756,9 +728,7 @@ "en": "Map of ambulance stations", "ja": "救急ステーションの地図", "ru": "Карта станций скорой помощи", - "fr": "Couche des ambulances", - "de": "Karte der Rettungswachen", - "it": "Carta delle stazioni delle ambulanze" + "fr": "Couche des ambulances" }, "minzoom": 12, "source": { @@ -773,17 +743,13 @@ "en": "Ambulance Station", "ru": "Станция скорой помощи", "ja": "救急ステーション", - "fr": "Station d’ambulances", - "de": "Rettungswache", - "it": "Stazione delle ambulanze" + "fr": "Station d’ambulances" } }, "description": { "en": "An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies.", "ja": "救急ステーションは、救急車、医療機器、個人用保護具、およびその他の医療用品を保管する場所です。", - "fr": "Une station d’ambulance est un lieu où sont stockés les véhicules d’urgence ainsi que de l’équipement médical.", - "de": "Eine Rettungswache ist ein Ort, an dem Rettungsfahrzeuge, medizinische Ausrüstung, persönliche Schutzausrüstung und anderes medizinisches Material untergebracht sind.", - "it": "La stazione delle ambulanze è un’area per lo stoccaggio delle ambulanze, dell’equipaggiamento medico, dei dispositivi di protezione individuale e di altre forniture medicali." + "fr": "Une station d’ambulance est un lieu où sont stockés les véhicules d’urgence ainsi que de l’équipement médical." }, "tagRenderings": [ { @@ -795,15 +761,13 @@ "en": "What is the name of this ambulance station?", "ja": "この救急ステーションの名前は何ですか?", "ru": "Как называется эта станция скорой помощи?", - "fr": "Quel est le nom de cette station ?", - "it": "Qual è il nome di questa stazione delle ambulanze?" + "fr": "Quel est le nom de cette station ?" }, "render": { "en": "This station is called {name}.", "ja": "このステーションの名前は{name}です。", "ru": "Эта станция называется {name}.", - "fr": "Cette station s’appelle {name}.", - "it": "Questa stazione è chiamata {name}." + "fr": "Cette station s’appelle {name}." } }, { @@ -815,15 +779,13 @@ "en": " What is the street name where the station located?", "ja": " 救急ステーションの所在地はどこですか?", "ru": " По какому адресу расположена эта станция?", - "fr": " Quel est le nom de la rue où la station se situe ?", - "it": " Come si chiama la strada in cui si trova questa stazione?" + "fr": " Quel est le nom de la rue où la station se situe ?" }, "render": { "en": "This station is along a highway called {addr:street}.", "ja": "{addr:street} 沿いにあります。", "ru": "Эта станция расположена вдоль шоссе {addr:street}.", - "fr": "La station fait partie de {addr:street}.", - "it": "Questa stazione si trova in {addr:street}." + "fr": "La station fait partie de {addr:street}." } }, { @@ -832,8 +794,7 @@ "en": "Where is the station located? (e.g. name of neighborhood, villlage, or town)", "ja": "このステーションの住所は?(例: 地区、村、または町の名称)", "ru": "Где расположена станция? (напр., название населённого пункта)", - "fr": "Dans quelle localité la station est-elle située ?", - "it": "Dove si trova la stazione? (ad es. quartiere, paese o città)" + "fr": "Dans quelle localité la station est-elle située ?" }, "freeform": { "key": "addr:place" @@ -841,8 +802,7 @@ "render": { "en": "This station is found within {addr:place}.", "ja": "このステーションは{addr:place}にあります。", - "fr": "La station fait partie de {addr:place}.", - "it": "La stazione si trova a {addr:place}." + "fr": "La station fait partie de {addr:place}." } }, { @@ -850,14 +810,12 @@ "question": { "en": "What agency operates this station?", "ja": "このステーションを運営しているのはどこですか?", - "fr": "Quel est l’exploitant de la station ?", - "it": "Quale agenzia gestisce questa stazione?" + "fr": "Quel est l’exploitant de la station ?" }, "render": { "en": "This station is operated by {operator}.", "ja": "このステーションは{operator}によって運営されています。", - "fr": "Cette station est opérée par {operator}.", - "it": "Questa stazione è gestita da {operator}." + "fr": "Cette station est opérée par {operator}." }, "freeform": { "key": "operator" @@ -869,14 +827,12 @@ "question": { "en": "How is the station operator classified?", "ja": "ステーションの運営の分類は?", - "fr": "Quel est le type d’exploitant ?", - "it": "Com’è classificato il gestore della stazione?" + "fr": "Quel est le type d’exploitant ?" }, "render": { "en": "The operator is a(n) {operator:type} entity.", "ja": "運営者は、{operator:type} です。", - "fr": "L’exploitant est de type {operator:type}.", - "it": "L’operatore è un ente {operator:type}." + "fr": "L’exploitant est de type {operator:type}." }, "freeform": { "key": "operator:type" @@ -891,8 +847,7 @@ "then": { "en": "The station is operated by the government.", "ja": "ステーションは自治体が運営する。", - "fr": "La station est opérée par le gouvernement.", - "it": "La stazione è gestita dal governo." + "fr": "La station est opérée par le gouvernement." } }, { @@ -904,8 +859,7 @@ "then": { "en": "The station is operated by a community-based, or informal organization.", "ja": "任意団体やコミュニティが運営しているステーションである。", - "fr": "La station est opérée par une organisation informelle.", - "it": "La stazione è gestita dalla comunità o un’organizzazione non ufficiale." + "fr": "La station est opérée par une organisation informelle." } }, { @@ -917,8 +871,7 @@ "then": { "en": "The station is operated by a formal group of volunteers.", "ja": "公益団体が運営しているステーションである。", - "fr": "La station est opérée par un groupe officiel de bénévoles.", - "it": "La stazione è gestita da un gruppo ufficiale di volontari." + "fr": "La station est opérée par un groupe officiel de bénévoles." } }, { @@ -930,26 +883,13 @@ "then": { "en": "The station is privately operated.", "ja": "個人が運営しているステーションである。", - "fr": "La station est opérée par un groupe privé.", - "it": "La stazione è gestita da un privato." + "fr": "La station est opérée par un groupe privé." } } ] }, "images" ], - "icon": { - "render": "./assets/themes/hailhydrant/Twemoji_1f691.svg" - }, - "width": { - "render": "1" - }, - "iconSize": { - "render": "35,35,center" - }, - "color": { - "render": "#00f" - }, "presets": [ { "tags": [ @@ -959,21 +899,39 @@ "en": "Ambulance station", "ru": "Станция скорой помощи", "ja": "救急ステーション(消防署)", - "fr": "Station d’ambulances", - "de": "Rettungswache", - "it": "Stazione delle ambulanze" + "fr": "Station d’ambulances" }, "description": { "en": "Add an ambulance station to the map", "ja": "救急ステーション(消防署)をマップに追加する", "ru": "Добавить станцию скорой помощи на карту", "fr": "Ajouter une station d’ambulances à la carte", - "de": "Eine Rettungsstation der Karte hinzufügen", - "it": "Aggiungi una stazione delle ambulanza alla mappa" + "de": "Eine Rettungsstation der Karte hinzufügen" } } ], - "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 57e4af2f6..3ed14ee9e 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -5,8 +5,8 @@ "nl": "Natuurgebieden", "en": "Nature Reserves", "de": "Naturschutzgebiete", - "it": "Riserve naturali", - "nb_NO": "Naturreservater" + "nb_NO": "Naturreservater", + "it": "Riserve naturali" }, "shortDescription": { "nl": "Deze kaart toont de natuurgebieden van Natuurpunt", @@ -17,8 +17,8 @@ "description": { "nl": "Op deze kaart vind je alle natuurgebieden die Natuurpunt ter beschikking stelt", "en": "On this map you can find all the nature reserves that Natuurpunt offers ", - "de": "Auf dieser Karte können Sie alle Naturschutzgebiete von Natuurpunt finden ", - "it": "In questa cartina è possibile trovare tutte le riserve naturali offerte da Natuupunt. " + "it": "In questa cartina è possibile trovare tutte le riserve naturali offerte da Natuupunt. ", + "de": "Auf dieser Karte können Sie alle Naturschutzgebiete von Natuurpunt finden " }, "language": [ "nl", @@ -42,9 +42,9 @@ "enablePdfDownload": true, "enableDownload": true, "hideFromOverview": true, - "#": "Disable clustering for this theme", "clustering": { - "maxZoom": 0 + "maxZoom": 0, + "#": "Disable clustering for this theme" }, "layers": [ { diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index f777703d0..a6b9f2c5c 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -37,56 +37,39 @@ "en": "wind turbine", "nl": "windturbine", "fr": "Éolienne", - "de": "Windrad", - "it": "pala eolica" + "de": "Windrad" }, "source": { "osmTags": "generator:source=wind" }, "minzoom": 10, - "wayHandling": 1, "title": { "render": { "en": "wind turbine", "nl": "windturbine", "fr": "éolienne", - "de": "Windrad", - "it": "pala eolica" + "de": "Windrad" }, "mappings": [ { "if": "name~*", "then": { "en": "{name}", - "fr": "{name}", - "eo": "{name}", - "it": "{name}" + "fr": "{name}" } } ] }, - "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", - "iconSize": "40, 40, bottom", - "label": { - "mappings": [ - { - "if": "generator:output:electricity~^[0-9]+.*[W]$", - "then": "
    {generator:output:electricity}
    " - } - ] - }, "tagRenderings": [ { "id": "turbine-output", "render": { "en": "The power output of this wind turbine is {generator:output:electricity}.", - "fr": "La puissance générée par cette éolienne est de {generator:output:electricity}.", - "it": "La potenza generata da questa pala eolica è {generator:output:electricity}." + "fr": "La puissance générée par cette éolienne est de {generator:output:electricity}." }, "question": { "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)", - "fr": "Quel est la puissance générée par cette éolienne ?", - "it": "Quant’è la potenza generata da questa pala eolica? (ad es. 2.3 MW)" + "fr": "Quel est la puissance générée par cette éolienne ?" }, "freeform": { "key": "generator:output:electricity", @@ -97,13 +80,11 @@ "id": "turbine-operator", "render": { "en": "This wind turbine is operated by {operator}.", - "fr": "Cette éolienne est opérée par {operator}.", - "it": "Questa pala eolica è gestita da {operator}." + "fr": "Cette éolienne est opérée par {operator}." }, "question": { "en": "Who operates this wind turbine?", - "fr": "Qui est l’exploitant de cette éolienne ?", - "it": "Chi gestisce questa pala eolica?" + "fr": "Qui est l’exploitant de cette éolienne ?" }, "freeform": { "key": "operator" @@ -113,13 +94,11 @@ "id": "turbine-height", "render": { "en": "The total height (including rotor radius) of this wind turbine is {height} metres.", - "fr": "La hauteur totale, incluant les pales, est de {height} mètres.", - "it": "L’altezza totale (raggio del rotore incluso) di questa pala eolica è di {height} metri." + "fr": "La hauteur totale, incluant les pales, est de {height} mètres." }, "question": { "en": "What is the total height of this wind turbine (including rotor radius), in metres?", - "fr": "Quelle est la hauteur totale de l’éolienne en mètres, pales incluses ?", - "it": "Qual è l’altezza (in metri e raggio del rotore incluso) di questa pala eolica?" + "fr": "Quelle est la hauteur totale de l’éolienne en mètres, pales incluses ?" }, "freeform": { "key": "height", @@ -130,13 +109,11 @@ "id": "turbine-diameter", "render": { "en": "The rotor diameter of this wind turbine is {rotor:diameter} metres.", - "fr": "Le diamètre du rotor est de {rotor:diameter} mètres.", - "it": "Il diametro del rotore di questa pala eolica è di {rotor:diameter} metri." + "fr": "Le diamètre du rotor est de {rotor:diameter} mètres." }, "question": { "en": "What is the rotor diameter of this wind turbine, in metres?", - "fr": "Quel est le diamètre du rotor en mètres ?", - "it": "Qual è il diametro (in metri) del rotore di questa pala eolica?" + "fr": "Quel est le diamètre du rotor en mètres ?" }, "freeform": { "key": "rotor:diameter", @@ -147,13 +124,11 @@ "id": "turbine-start-date", "render": { "en": "This wind turbine went into operation on/in {start_date}.", - "fr": "L’éolienne est active depuis {start_date}.", - "it": "Questa pala eolica è entrata in funzione in data {start_date}." + "fr": "L’éolienne est active depuis {start_date}." }, "question": { "en": "When did this wind turbine go into operation?", - "fr": "Depuis quand l’éolienne est-elle en fonctionnement ?", - "it": "Quando è entrata in funzione questa pala eolica?" + "fr": "Depuis quand l’éolienne est-elle en fonctionnement ?" }, "freeform": { "key": "start_date", @@ -172,8 +147,7 @@ "en": "wind turbine", "nl": "windturbine", "fr": "Éolienne", - "de": "Windrad", - "it": "pala eolica" + "de": "Windrad" } } ], @@ -193,9 +167,7 @@ "en": " megawatts", "nl": " megawatt", "fr": " megawatts", - "de": " Megawatt", - "eo": " megavatoj", - "it": " megawatt" + "de": " Megawatt" } }, { @@ -208,10 +180,7 @@ "en": " kilowatts", "nl": " kilowatt", "fr": " kilowatts", - "de": " Kilowatt", - "eo": " kilovatoj", - "it": " kilowatt", - "nb_NO": " kilowatt" + "de": " Kilowatt" } }, { @@ -224,9 +193,7 @@ "en": " watts", "nl": " watt", "fr": " watts", - "de": " Watt", - "eo": " vatoj", - "it": " watt" + "de": " Watt" } }, { @@ -239,9 +206,7 @@ "en": " gigawatts", "nl": " gigawatt", "fr": " gigawatts", - "de": " Gigawatt", - "eo": " gigavatoj", - "it": " gigawatt" + "de": " Gigawatt" } } ], @@ -262,13 +227,28 @@ "en": " meter", "nl": " meter", "fr": " mètres", - "de": " Meter", - "eo": " metro", - "it": " metri" + "de": " Meter" } } ] } + ], + "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 a7b2a558d..086b5f423 100644 --- a/assets/themes/postboxes/postboxes.json +++ b/assets/themes/postboxes/postboxes.json @@ -3,8 +3,8 @@ "title": { "en": "Postbox and Post Office Map", "de": "Karte mit Briefkästen und Poststellen", - "it": "Buche delle lettere e uffici postali", - "nb_NO": "Postboks og postkontor-kart" + "nb_NO": "Postboks og postkontor-kart", + "it": "Buche delle lettere e uffici postali" }, "shortDescription": { "en": "A map showing postboxes and post offices", @@ -38,9 +38,7 @@ "id": "postboxes", "name": { "en": "Postboxes", - "de": "Brieflästen", - "it": "Buche delle lettere", - "nb_NO": "Postbokser" + "de": "Brieflästen" }, "minzoom": 12, "source": { @@ -49,16 +47,12 @@ "title": { "render": { "en": "Postbox", - "de": "Briefkasten", - "it": "Buca delle lettere", - "nb_NO": "Postboks" + "de": "Briefkasten" } }, "description": { "en": "The layer showing postboxes.", - "de": "Die Ebene zeigt Briefkästen.", - "it": "Il livello che mostra le buche delle lettere.", - "nb_NO": "Laget viser postbokser." + "de": "Die Ebene zeigt Briefkästen." }, "tagRenderings": [ "images", @@ -67,18 +61,6 @@ "render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }" } ], - "icon": { - "render": "./assets/themes/postboxes/postbox.svg" - }, - "width": { - "render": "1" - }, - "iconSize": { - "render": "40,40,bottom" - }, - "color": { - "render": "#DADADA" - }, "presets": [ { "tags": [ @@ -86,13 +68,10 @@ ], "title": { "en": "postbox", - "de": "Briefkasten", - "it": "Buca delle lettere", - "nb_NO": "postboks" + "de": "Briefkasten" } } ], - "wayHandling": 2, "deletion": { "softDeletionTags": { "and": [ @@ -100,15 +79,35 @@ "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", - "it": "Uffici postali", - "nb_NO": "Postkontor" + "de": "Poststellen" }, "minzoom": 12, "source": { @@ -117,16 +116,12 @@ "title": { "render": { "en": "Post Office", - "de": "Poststelle", - "it": "Ufficio postale", - "nb_NO": "Postkontor" + "de": "Poststelle" } }, "description": { "en": "A layer showing post offices.", - "de": "Eine Ebene mit Postämtern.", - "it": "Un livello che mostra gli uffici postali.", - "nb_NO": "Et lag som viser postkontor." + "de": "Eine Ebene mit Postämtern." }, "tagRenderings": [ "images", @@ -136,49 +131,27 @@ }, { "render": { - "en": "Opening Hours: {opening_hours_table()}", - "it": "Orari di apertura: {opening_hours_table()}" + "en": "Opening Hours: {opening_hours_table()}" }, "freeform": { "key": "opening_hours", "type": "opening_hours" }, "question": { - "en": "What are the opening hours for this post office?", - "it": "Quali sono gli orari di apertura di questo ufficio postale?" + "en": "What are the opening hours for this post office?" }, "mappings": [ { "if": "opening_hours=24/7", "then": { "en": "24/7 opened (including holidays)", - "de": "durchgehend geöffnet (auch an Feiertagen)", - "it": "aperto 24/24h (feste incluse)" + "de": "durchgehend geöffnet (auch an Feiertagen)" } } ], "id": "OH" } ], - "icon": { - "render": "square:white;./assets/themes/postboxes/post_office.svg" - }, - "iconOverlays": [ - { - "if": "opening_hours~*", - "then": "isOpen", - "badge": true - } - ], - "width": { - "render": "1" - }, - "iconSize": { - "render": "40,40,bottom" - }, - "color": { - "render": "#DADADA" - }, "presets": [ { "tags": [ @@ -186,13 +159,10 @@ ], "title": { "en": "Post Office", - "de": "Poststelle", - "it": "Ufficio postale", - "nb_NO": "Postkontor" + "de": "Poststelle" } } ], - "wayHandling": 2, "filter": [ { "id": "is_open", @@ -200,13 +170,40 @@ { "question": { "en": "Currently open", - "de": "Aktuell geöffnet", - "it": "Aperto adesso" + "de": "Aktuell geöffnet" }, "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" + } + } ] } ] diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json new file mode 100644 index 000000000..f48503f39 --- /dev/null +++ b/assets/themes/sidewalks/sidewalks.json @@ -0,0 +1,181 @@ +{ + "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=unclassified", + "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 + } + ] +} diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index d25d24cf9..ad8255ed4 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -36,7 +36,15 @@ "color": "#444444", "width": { "render": "1" - } + }, + "mapRendering": [ + { + "color": "#444444", + "width": { + "render": "1" + } + } + ] }, { "builtin": "play_forest", @@ -252,7 +260,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/street_lighting/street_lighting.json b/assets/themes/street_lighting/street_lighting.json new file mode 100644 index 000000000..8ba9ebd51 --- /dev/null +++ b/assets/themes/street_lighting/street_lighting.json @@ -0,0 +1,179 @@ +{ + "id": "street_lighting", + "maintainer": "Robin van der Linde", + "version": "2021-10-22", + "language": [ + "en", + "nl" + ], + "title": { + "en": "Street Lighting", + "nl": "Straatverlichting" + }, + "description": { + "en": "On this map you can find everything about street lighting", + "nl": "Op deze kaart vind je alles over straatlantaarns" + }, + "icon": "./assets/layers/street_lamps/street_lamp.svg", + "startZoom": 19, + "startLat": 52.99319, + "startLon": 6.56113, + "layers": [ + "street_lamps", + { + "id": "lit_streets", + "name": { + "en": "Lit streets", + "nl": "Verlichte straten" + }, + "source": { + "osmTags": { + "and": [ + "highway!=", + "lit!=no", + "lit!=", + "service!=driveway" + ] + } + }, + "minZoom": 16, + "title": { + "render": { + "en": "Lit street", + "nl": "Verlichte straat" + }, + "mappings": [ + { + "if": "name~*", + "then": "{name}" + } + ] + }, + "mapRendering": [ + { + "color": "#ff0" + } + ], + "tagRenderings": [ + { + "id": "lit", + "question": { + "en": "Is this street lit?", + "nl": "Is deze straat verlicht?" + }, + "mappings": [ + { + "if": "lit=yes", + "then": { + "en": "This street is lit", + "nl": "Deze straat is verlicht" + } + }, + { + "if": "lit=no", + "then": { + "en": "This street is not lit", + "nl": "Deze straat is niet verlicht" + } + }, + { + "if": "lit=sunset-sunrise", + "then": { + "en": "This street is lit at night", + "nl": "Deze straat is 's nachts verlicht" + }, + "hideInAnswer": true + }, + { + "if": "lit=24/7", + "then": { + "en": "This street is lit 24/7", + "nl": "Deze straat is 24/7 verlicht" + } + } + ] + } + ], + "allowSplit": true + }, + { + "id": "all_streets", + "name": { + "en": "All streets", + "nl": "Alle straten" + }, + "source": { + "osmTags": { + "and": ["highway!=", "service!=driveway", "highway!=platform"] + } + }, + "minZoom": 19, + "title": { + "render": { + "en": "Street", + "nl": "Straat" + }, + "mappings": [ + { + "if": "name~*", + "then": "{name}" + } + ] + }, + "mapRendering": [ + { + "color": { + "render":"#a9a9a9", + "mappings": [ + { + "if":"lit=no", + "then": "#303030" + } + ] + } + } + ], + "tagRenderings": [ + { + "id": "lit", + "question": { + "en": "Is this street lit?", + "nl": "Is deze straat verlicht?" + }, + "mappings": [ + { + "if": "lit=yes", + "then": { + "en": "This street is lit", + "nl": "Deze straat is verlicht" + } + }, + { + "if": "lit=no", + "then": { + "en": "This street is not lit", + "nl": "Deze straat is niet verlicht" + } + }, + { + "if": "lit=sunset-sunrise", + "then": { + "en": "This street is lit at night", + "nl": "Deze straat is 's nachts verlicht" + }, + "hideInAnswer": true + }, + { + "if": "lit=24/7", + "then": { + "en": "This street is lit 24/7", + "nl": "Deze straat is 24/7 verlicht" + } + } + ] + } + ], + "allowSplit": true + } + ] +} \ No newline at end of file diff --git a/assets/themes/street_lighting/street_lighting_assen.json b/assets/themes/street_lighting/street_lighting_assen.json new file mode 100644 index 000000000..695347f42 --- /dev/null +++ b/assets/themes/street_lighting/street_lighting_assen.json @@ -0,0 +1,58 @@ +{ + "id": "street_lighting_assen", + "maintainer": "Robin van der Linde", + "version": "2021-10-22", + "language": [ + "nl", + "en" + ], + "title": { + "nl": "Straatverlichting - Assen" + }, + "description": { + "nl": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen" + }, + "icon": "./assets/layers/street_lamps/street_lamp.svg", + "startZoom": 19, + "startLat": 52.99319, + "startLon": 6.56113, + "layers": [ + "street_lamps", + { + "id": "Assen", + "name": "Dataset Assen", + "source": { + "osmTags": "Lichtmastnummer~*", + "#geoJson": "https://opendata.arcgis.com/datasets/ba37cdb372064b3199c548b75d16a609_0.geojson", + "geoJson": "https://robinlinde.github.io/tiles/assen_street_lighting/{z}/{x}/{y}.json", + "geoJsonZoomLevel": 16, + "isOsmCache": false + }, + "calculatedTags": [ + "_closest_osm_street_lamp=feat.closest('street_lamps')?.properties?.id", + "_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp) * 1000", + "_has_closeby_feature=Number(feat.properties._closest_osm_street_lamp_distance) < 5 ? 'yes' : 'no'" + ], + "title": "Straatlantaarn in dataset", + "mapRendering": [ + { + "location": "point", + "icon": { + "render": "circle:red", + "mappings": [ + { + "if": "_has_closeby_feature=yes", + "then": "circle:#008000aa" + } + ] + }, + "iconSize": "20,20,center" + } + ], + "tagRenderings": [ + "all_tags" + ] + } + ], + "hideFromOverview": true +} \ No newline at end of file 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 9f38e0bd3..2c4753068 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -46,16 +46,42 @@ "defaultState": false, "name": { "en": "Property boundaries by osmuk.org", - "de": "Grenzverläufe gemäß osmuk.org", - "it": "Confini delle proprietà di osmuk.org" + "de": "Grenzverläufe gemäß osmuk.org" } } ], "layers": [ + { + "id": "raw_inspire_polygons", + "source": { + "geoJson": "https://osm-uk-addresses.russss.dev/inspire/{z}/{x}/{y}.json", + "osmTags": "inspireid~*", + "geoJsonZoomLevel": 18, + "isOsmCache": false + }, + "minzoom": 18, + "calculatedTags": [ + "_has_address=feat.overlapWith('addresses').length > 0" + ], + "#mapRendering": [ + { + "width": 2, + "color": { + "render": "#00f", + "mappings": [ + { + "if": "_has_address=true", + "then": "#0f0" + } + ] + } + } + ], + "mapRendering": [] + }, { "id": "to_import", "source": { - "#geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/assets/themes/uk_addresses/islington_small_piece.geojson", "geoJson": "https://osm-uk-addresses.russss.dev/addresses/{z}/{x}/{y}.json", "osmTags": "inspireid~*", "geoJsonZoomLevel": 16, @@ -63,23 +89,6 @@ }, "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" - }, - { - "if": "_imported=yes", - "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg" - } - ] - }, - "iconSize": { - "render": "40,40,center" - }, "title": { "render": "Address to be determined" }, @@ -91,18 +100,29 @@ { "id": "uk_addresses_embedding_outline", "render": "An outline embedding this point with an address already exists in OpenStreetMap.
    This object has address {_embedding_object:addr:street} {_embedding_object:addr:housenumber}", + "mappings": [ + { + "if": "_embedding_object:id=true", + "then": "The INSPIRE-polygon containing this point has at least one address contained" + }, + { + "if": "_embedding_object:id=false", + "then": "The INSPIRE-polygon containing this point has no addresses contained" + } + ], "condition": "_embedding_object:id~*" }, { "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": [ "_embedding_object=feat.overlapWith('addresses')[0]?.feat?.properties ?? null", "_embedding_object:addr:housenumber=JSON.parse(feat.properties._embedding_object)?.['addr:housenumber']", "_embedding_object:addr:street=JSON.parse(feat.properties._embedding_object)?.['addr:street']", - "_embedding_object:id=JSON.parse(feat.properties._embedding_object)?.id" + "_embedding_inspire_polygon_has_address=feat.overlapWith('raw_inspire_polygons')[0]?.feat?.properties?._has_address", + "_embedding_object:id=feat.get('_embedding_object')?.id ?? feat.properties._embedding_inspire_polygon_has_address" ], "filter": [ { @@ -113,21 +133,52 @@ "osmTags": { "and": [ "_imported=", - "_embedding_object:id=" + { + "or": [ + "_embedding_object:id=", + "_embedding_object:id=false" + ] + } ] } } ] } + ], + "mapRendering": [ + { + "icon": { + "render": "./assets/themes/uk_addresses/housenumber_unknown.svg", + "mappings": [ + { + "if": { + "and": [ + "_embedding_object:id~*", + "_embedding_object:id!=false" + ] + }, + "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" + }, + "location": [ + "point" + ] + } ] }, { "id": "addresses", "name": { "en": "Known addresses in OSM", - "de": "Bekannte Adressen in OSM", - "it": "Indirizzo presente su OSM", - "nb_NO": "Kjente adresser i OSM" + "de": "Bekannte Adressen in OSM" }, "minzoom": 18, "source": { @@ -148,25 +199,20 @@ "title": { "render": { "en": "Known address", - "de": "Bekannte Adresse", - "it": "Indirizzo conosciuto", - "nb_NO": "Kjent adresse" + "de": "Bekannte Adresse" } }, "description": { "en": "Addresses", "nl": "Adressen", - "de": "Adressen", - "it": "Indirizzi", - "nb_NO": "Adresser" + "de": "Adressen" }, "tagRenderings": [ { "id": "uk_addresses_explanation_osm", "render": { "en": "This address is saved in OpenStreetMap", - "de": "Diese Adresse ist in OpenStreetMap gespeichert", - "it": "Questo indirizzo è salvato su OpenStreetMap" + "de": "Diese Adresse ist in OpenStreetMap gespeichert" } }, { @@ -174,13 +220,11 @@ "render": { "en": "The housenumber is {addr:housenumber}", "nl": "Het huisnummer is {addr:housenumber}", - "de": "Die Hausnummer ist {addr:housenumber}", - "it": "Il numero civico è {addr:housenumber}" + "de": "Die Hausnummer ist {addr:housenumber}" }, "question": { "en": "What is the number of this house?", - "de": "Wie lautet die Nummer dieses Hauses?", - "it": "Qual è il numero civico di questa casa?" + "de": "Wie lautet die Nummer dieses Hauses?" }, "freeform": { "key": "addr:housenumber", @@ -198,23 +242,47 @@ "then": { "en": "This building has no house number", "nl": "Dit gebouw heeft geen huisnummer", - "de": "Dieses Gebäude hat keine Hausnummer", - "it": "Questo edificio non ha indirizzo" + "de": "Dieses Gebäude hat keine Hausnummer" } } ] }, + { + "id": "uk_addresses_housename", + "question": "What is the name of this house?
    This is normally indicated on a plaque.
    Do NOT add names of inhabitants!
    ", + "render": "This house is named {addr:housename}", + "freeform": { + "key": "addr:housename", + "addExtraTags": [ + "nohousename=" + ] + }, + "mappings": [ + { + "if": "nohousename=yes", + "then": "This building has no housename" + }, + { + "if": { + "and": [ + "addr:housename=", + "nohousenumber!=yes" + ] + }, + "then": "This building has no housename", + "hideInAnswer": true + } + ] + }, { "id": "uk_addresses_street", "render": { "en": "This address is in street {addr:street}", - "de": "Diese Adresse befindet sich in der Straße {addr:street}", - "it": "L’indirizzo è in via {addr:street}" + "de": "Diese Adresse befindet sich in der Straße {addr:street}" }, "question": { "en": "What street is this address located in?", - "de": "In welcher Straße befindet sich diese Adresse?", - "it": "Qual è la via in cui si trova?" + "de": "In welcher Straße befindet sich diese Adresse?" }, "freeform": { "key": "addr:street" @@ -241,52 +309,84 @@ "nohousenumber!~yes" ] } + }, + { + "id": "fixme", + "render": "Fixme description{render}", + "question": { + "en": "What should be fixed here? Please explain" + }, + "freeform": { + "key": "fixme" + }, + "mappings": [ + { + "if": "fixme=", + "then": "No fixme - write something here to explain complicated cases" + } + ] + }, + "questions", + { + "id": "address-sign-image", + "render": { + "en": "{image_carousel(image:address)}
    {image_upload(image:address, Add image of the address)}" + } } ], - "icon": { - "render": "./assets/themes/uk_addresses/housenumber_ok.svg", - "mappings": [ - { - "if": { - "or": [ - { - "and": [ - "addr:housenumber=", - "nohousenumber!=yes" + "mapRendering": [ + { + "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" - } - ] - }, - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f", - "mappings": [ - { - "if": { - "or": [ - { - "and": [ - "addr:housenumber=", - "nohousenumber!=yes" + "then": "./assets/themes/uk_addresses/housenumber_unknown.svg" + } + ] + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "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", @@ -299,12 +399,18 @@ ] } }, - "color": { - "render": "#ccc" - }, - "width": { - "render": "0" - } + "mapRendering": [ + { + "color": { + "render": "#ccc" + }, + "width": { + "render": "0" + } + } + ] } - ] + ], + "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/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 a383eaac2..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() @@ -65,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(() => { @@ -78,13 +79,8 @@ class Init { .GetLongPreference("installed-theme-" + layoutToUse.id) .setData(encoded); }); - } - - } - - } diff --git a/langs/en.json b/langs/en.json index db9b37823..e93a28ade 100644 --- a/langs/en.json +++ b/langs/en.json @@ -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", @@ -158,7 +159,15 @@ }, "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.", @@ -209,10 +218,10 @@ }, "opening_hours": { "error_loading": "Error: could not visualize these opening hours.", - "open_during_ph": "During a public holiday, this amenity is", + "open_during_ph": "During a public holiday, this is", "opensAt": "from", "openTill": "till", - "not_all_rules_parsed": "The opening hours of this shop are complicated. The following rules are ignored in the input element:", + "not_all_rules_parsed": "These opening hours are complicated. The following rules are ignored in the input element:", "closed_until": "Closed until {date}", "closed_permanently": "Closed for an unkown duration", "open_24_7": "Opened around the clock", @@ -234,6 +243,10 @@ "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": { diff --git a/langs/layers/de.json b/langs/layers/de.json index d4bea545f..05993e3ec 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -923,6 +923,311 @@ } } }, + "charging_station": { + "filter": { + "0": { + "options": { + "0": { + "question": "Alle Fahrzeugtypen" + }, + "1": { + "question": "Ladestation für Fahrräder" + }, + "2": { + "question": "Ladestation für Autos" + } + } + }, + "1": { + "options": { + "0": { + "question": "Nur funktionierende Ladestationen" + } + } + }, + "2": { + "options": { + "0": { + "question": "Alle Anschlüsse" + }, + "3": { + "question": "Hat einen
    Chademo
    Stecker" + }, + "7": { + "question": "Hat einen
    Tesla Supercharger
    Stecker" + } + } + } + }, + "presets": { + "0": { + "title": "Ladestation" + } + }, + "tagRenderings": { + "Auth phone": { + "question": "Wie lautet die Telefonnummer für den Authentifizierungsanruf oder die SMS?", + "render": "Authentifizierung durch Anruf oder SMS an {authentication:phone_call:number}" + }, + "Authentication": { + "mappings": { + "0": { + "then": "Authentifizierung durch eine Mitgliedskarte" + }, + "1": { + "then": "Authentifizierung durch eine App" + }, + "2": { + "then": "Authentifizierung per Anruf ist möglich" + }, + "3": { + "then": "Authentifizierung per Anruf ist möglich" + }, + "4": { + "then": "Authentifizierung über NFC ist möglich" + }, + "5": { + "then": "Authentifizierung über Geldkarte ist möglich" + }, + "6": { + "then": "Authentifizierung per Debitkarte ist möglich" + }, + "7": { + "then": "Das Aufladen ist hier (auch) ohne Authentifizierung möglich" + } + }, + "question": "Welche Authentifizierung ist an der Ladestation möglich?" + }, + "Available_charging_stations (generated)": { + "mappings": { + "5": { + "then": "
    Chademo
    " + }, + "6": { + "then": "
    Typ 1 mit Kabel (J1772)
    " + }, + "7": { + "then": "
    Typ 1 mit Kabel (J1772)
    " + }, + "8": { + "then": "
    Typ 1 ohne Kabel (J1772)
    " + }, + "9": { + "then": "
    Typ 1 ohne Kabel (J1772)
    " + }, + "10": { + "then": "
    Typ 1 CCS (auch bekannt als Typ 1 Combo)
    " + }, + "11": { + "then": "
    Typ 1 CCS (auch bekannt als Typ 1 Combo)
    " + }, + "12": { + "then": "
    Tesla Supercharger
    " + }, + "13": { + "then": "
    Tesla Supercharger
    " + }, + "14": { + "then": "
    Typ 2 (Mennekes)
    " + }, + "15": { + "then": "
    Typ 2 (Mennekes)
    " + }, + "16": { + "then": "
    Typ 2 CCS (Mennekes)
    " + }, + "17": { + "then": "
    Typ 2 CCS (Mennekes)
    " + }, + "18": { + "then": "
    Typ 2 mit Kabel (Mennekes)
    " + }, + "19": { + "then": "
    Typ 2 mit Kabel (Mennekes)
    " + }, + "20": { + "then": "
    Tesla Supercharger CCS (Typ 2 CSS)
    " + }, + "21": { + "then": "
    Tesla Supercharger CCS (Typ 2 CSS)
    " + }, + "26": { + "then": "
    USB zum Laden von Smartphones oder Elektrokleingeräten
    " + }, + "27": { + "then": "
    USB zum Laden von Smartphones und Elektrokleingeräten
    " + }, + "30": { + "then": "
    Bosch Active Connect mit 5 Pins und Kabel
    " + }, + "31": { + "then": "
    Bosch Active Connect mit 5 Pins und Kabel
    " + } + }, + "question": "Welche Ladestationen gibt es hier?" + }, + "Network": { + "mappings": { + "0": { + "then": "Nicht Teil eines größeren Netzwerks" + }, + "1": { + "then": "Nicht Teil eines größeren Netzwerks" + } + }, + "question": "Ist diese Ladestation Teil eines Netzwerks?", + "render": "Teil des Netzwerks {network}" + }, + "OH": { + "mappings": { + "0": { + "then": "durchgehend geöffnet (auch an Feiertagen)" + } + }, + "question": "Wann ist diese Ladestation geöffnet?" + }, + "Operational status": { + "mappings": { + "0": { + "then": "Diese Ladestation funktioniert" + }, + "1": { + "then": "Diese Ladestation ist kaputt" + }, + "2": { + "then": "Hier ist eine Ladestation geplant" + }, + "3": { + "then": "Hier wird eine Ladestation gebaut" + }, + "4": { + "then": "Diese Ladestation wurde dauerhaft deaktiviert und wird nicht mehr benutzt, ist aber noch sichtbar" + } + }, + "question": "Ist dieser Ladepunkt in Betrieb?" + }, + "Operator": { + "mappings": { + "0": { + "then": "Eigentlich ist {operator} das Netzwerk" + } + }, + "question": "Wer ist der Betreiber dieser Ladestation?", + "render": "Diese Ladestation wird betrieben von {operator}" + }, + "Parking:fee": { + "mappings": { + "0": { + "then": "Keine zusätzlichen Parkgebühren beim Laden" + }, + "1": { + "then": "Beim Laden ist eine zusätzliche Parkgebühr zu entrichten" + } + }, + "question": "Muss man beim Laden eine Parkgebühr bezahlen?" + }, + "Type": { + "mappings": { + "0": { + "then": "Fahrräder können hier geladen werden" + }, + "1": { + "then": "Autos können hier geladen werden" + }, + "2": { + "then": " Roller können hier geladen werden" + }, + "3": { + "then": "Lastkraftwagen (LKW) können hier geladen werden" + }, + "4": { + "then": "Busse können hier geladen werden" + } + }, + "question": "Welche Fahrzeuge dürfen hier geladen werden?" + }, + "access": { + "question": "Wer darf diese Ladestation benutzen?", + "render": "Zugang ist {access}" + }, + "capacity": { + "question": "Wie viele Fahrzeuge können hier gleichzeitig geladen werden?", + "render": "{capacity} Fahrzeuge können hier gleichzeitig geladen werden" + }, + "email": { + "question": "Wie ist die Email-Adresse des Betreibers?", + "render": "Bei Problemen senden Sie eine E-Mail an {email}" + }, + "maxstay": { + "mappings": { + "0": { + "then": "Keine Höchstparkdauer" + } + }, + "question": "Was ist die Höchstdauer des Aufenthalts hier?", + "render": "Die maximale Parkzeit beträgt {canonical(maxstay)}" + }, + "payment-options": { + "override": { + "mappings+": { + "0": { + "then": "Bezahlung mit einer speziellen App" + }, + "1": { + "then": "Bezahlung mit einer Mitgliedskarte" + } + } + } + }, + "phone": { + "question": "Welche Nummer kann man anrufen, wenn es ein Problem mit dieser Ladestation gibt?", + "render": "Bei Problemen, anrufen unter {phone}" + }, + "ref": { + "question": "Wie lautet die Kennung dieser Ladestation?", + "render": "Die Kennziffer ist {ref}" + }, + "website": { + "question": "Wie ist die Webseite des Betreibers?", + "render": "Weitere Informationen auf {website}" + } + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": " Minuten", + "humanSingular": " Minute" + }, + "1": { + "human": " Stunden", + "humanSingular": " Stunde" + }, + "2": { + "human": " Tage", + "humanSingular": " Tag" + } + } + }, + "1": { + "applicableUnits": { + "0": { + "human": "Volt" + } + } + }, + "3": { + "applicableUnits": { + "0": { + "human": "Kilowatt" + }, + "1": { + "human": "Megawatt" + } + } + } + } + }, "crossings": { "description": "Übergänge für Fußgänger und Radfahrer", "name": "Kreuzungen", @@ -950,11 +1255,8 @@ }, "crossing-button": { "mappings": { - "0": { - "then": "Diese Ampel hat eine Taste, um ein grünes Signal anzufordern" - }, "1": { - "then": "Diese Ampel hat keine Taste, um ein grünes Signal anzufordern" + "then": "Diese Ampel hat keine Taste, um ein grünes Signal anzufordern." } }, "question": "Hat diese Ampel eine Taste, um ein grünes Signal anzufordern?" @@ -1016,9 +1318,6 @@ }, "1": { "then": "Diese Kreuzung hat kein Blindenleitsystem" - }, - "2": { - "then": "Diese Kreuzung hat taktile Pflasterung, ist aber nicht korrekt" } }, "question": "Gibt es an dieser Kreuzung ein Blindenleitsystem?" @@ -1341,16 +1640,14 @@ "6": { "then": "Kein zusätzliches Verkehrszeichen vorhanden" } - }, - "question": "Hat das Verkehrszeichen D7 () ein Zusatzzeichen?" + } }, "cycleway-traffic-signs-supplementary": { "mappings": { "6": { "then": "Kein zusätzliches Verkehrszeichen vorhanden" } - }, - "question": "Hat das Verkehrszeichen D7 () ein Zusatzzeichen?" + } }, "cycleways_and_roads-cycleway:buffer": { "question": "Wie breit ist der Abstand zwischen Radweg und Straße?", @@ -1372,10 +1669,6 @@ } }, "question": "Ist diese Straße beleuchtet?" - }, - "width:carriageway": { - "question": "Wie groß ist die Fahrbahnbreite dieser Straße (in Metern)?
    Diese wird von Bordstein zu Bordstein gemessen und schließt daher die Breite von parallelen Parkspuren ein", - "render": "Die Fahrbahnbreite dieser Straße beträgt {width:carriageway}m" } }, "title": { @@ -1383,9 +1676,6 @@ "0": { "then": "Radweg" }, - "1": { - "then": "Gemeinsame Fahrspur" - }, "2": { "then": "Fahrradspur" }, @@ -1400,6 +1690,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Defibrillatoren", "presets": { "0": { @@ -1437,7 +1734,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": { @@ -1526,7 +1822,7 @@ "name": "Trinkwasserstelle", "presets": { "0": { - "title": "Trinkwasserstelle" + "title": "trinkwasser" } }, "tagRenderings": { @@ -1543,9 +1839,6 @@ }, "Still in use?": { "mappings": { - "0": { - "then": "Diese Trinkwasserstelle funktioniert" - }, "1": { "then": "Diese Trinkwasserstelle ist kaputt" }, @@ -1557,7 +1850,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": { @@ -1621,7 +1914,6 @@ "title": "Restaurant" }, "1": { - "description": "Ein Lebensmittelunternehmen, das sich auf schnellen Thekendienst und Essen zum Mitnehmen konzentriert", "title": "Schnellimbiss" }, "2": { @@ -1672,12 +1964,6 @@ "mappings": { "0": { "then": "Sie können ihre eigenen Behälter mitbringen, um Ihre Bestellung zu erhalten, was Einwegverpackungsmaterial und damit Abfall spart" - }, - "1": { - "then": "Das Mitbringen eines eigenen Containers ist nicht erlaubt" - }, - "2": { - "then": "Sie müssen Ihren eigenen Behälter mitbringen, um hier zu bestellen." } }, "question": "Wenn Sie Ihr eigenes Behältnis mitbringen (z. B. einen Kochtopf und kleine Töpfe), wird es dann zum Verpacken Ihrer Bestellung verwendet?
    " @@ -1720,7 +2006,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?", @@ -1740,8 +2026,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": { @@ -1811,8 +2096,7 @@ "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", - "render": "{curator} ist der Pfleger dieses Naturschutzgebietes" + "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": { @@ -1829,7 +2113,6 @@ "question": "Sind Hunde in diesem Naturschutzgebiet erlaubt?" }, "Email": { - "question": "An welche Email-Adresse kann man sich bei Fragen und Problemen zu diesem Naturschutzgebiet wenden?
    Respektieren Sie die Privatsphäre - geben Sie nur dann eine persönliche Email-Adresse an, wenn diese allgemein bekannt ist", "render": "{email}" }, "Surface area": { @@ -1839,7 +2122,6 @@ "question": "Auf welcher Webseite kann man mehr Informationen über dieses Naturschutzgebiet finden?" }, "phone": { - "question": "Welche Telefonnummer kann man bei Fragen und Problemen zu diesem Naturschutzgebiet anrufen?
    Respektieren Sie die Privatsphäre - geben Sie nur eine Telefonnummer an, wenn diese allgemein bekannt ist", "render": "{phone}" } } @@ -1859,7 +2141,6 @@ "then": "Eintritt kostenlos" } }, - "question": "Was kostet der Zugang zu diesem Turm?", "render": "Der Besuch des Turms kostet {charge}" }, "Height": { @@ -1983,11 +2264,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": { @@ -2009,7 +2288,6 @@ "render": "Betrieben von {operator}" }, "playground-phone": { - "question": "Wie lautet die Telefonnummer vom Betreiber des Spielplatzes?", "render": "{phone}" }, "playground-surface": { @@ -2282,9 +2560,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" }, @@ -2296,23 +2571,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": { @@ -2322,9 +2584,6 @@ }, "question": "Wann ist dieser Sportplatz zugänglich?" }, - "sport_pitch-phone": { - "question": "Wie ist die Telefonnummer des Betreibers?" - }, "sport_pitch-sport": { "mappings": { "0": { @@ -2367,7 +2626,6 @@ "then": "Die Oberfläche ist Beton" } }, - "question": "Was ist die Oberfläche dieses Sportplatzes?", "render": "Die Oberfläche ist {surface}" } }, @@ -2416,9 +2674,6 @@ }, "Surveillance type: public, outdoor, indoor": { "mappings": { - "0": { - "then": "Überwacht wird ein öffentlicher Bereich, z. B. eine Straße, eine Brücke, ein Platz, ein Park, ein Bahnhof, ein öffentlicher Korridor oder Tunnel,..." - }, "1": { "then": "Ein privater Außenbereich wird überwacht (z. B. ein Parkplatz, eine Tankstelle, ein Innenhof, ein Eingang, eine private Einfahrt, ...)" }, @@ -2554,25 +2809,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": { @@ -2630,27 +2866,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" } @@ -2722,14 +2937,8 @@ "1": { "then": "Als Denkmal registriert von der Direction du Patrimoine culturel Brüssel" }, - "2": { - "then": "Von einer anderen Organisation als Denkmal registriert" - }, "3": { "then": "Nicht als Denkmal registriert" - }, - "4": { - "then": "Von einer anderen Organisation als Denkmal registriert" } }, "question": "Ist dieser Baum ein Naturdenkmal?" @@ -2758,8 +2967,7 @@ "render": "Name: {name}" }, "tree_node-ref:OnroerendErfgoed": { - "question": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?", - "render": "" + "question": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?" }, "tree_node-wikidata": { "question": "Was ist das passende Wikidata Element zu diesem Baum?" @@ -2812,6 +3020,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Abfalleimer" + } + } + } + } + }, "name": "Abfalleimer", "presets": { "0": { @@ -2849,9 +3068,6 @@ }, "4": { "then": "Mülleimer für Drogen" - }, - "5": { - "then": "Ein Abfalleimer für Nadeln und andere scharfe Gegenstände" } }, "question": "Um was für einen Abfalleimer handelt es sich?" diff --git a/langs/layers/en.json b/langs/layers/en.json index b7565463f..280ecf940 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": { @@ -2922,6 +2929,154 @@ "render": "Sport pitch" } }, + "street_lamps": { + "name": "Street Lamps", + "presets": { + "0": { + "title": "street lamp" + } + }, + "tagRenderings": { + "colour": { + "mappings": { + "0": { + "then": "This lamp emits white light" + }, + "1": { + "then": "This lamp emits green light" + }, + "2": { + "then": "This lamp emits orange light" + } + }, + "question": "What colour light does this lamp emit?", + "render": "This lamp emits {light:colour} light" + }, + "count": { + "mappings": { + "0": { + "then": "This lamp has 1 fixture" + }, + "1": { + "then": "This lamp has 2 fixtures" + } + }, + "question": "How many fixtures does this light have?", + "render": "This lamp has {light:count} fixtures" + }, + "direction": { + "question": "Where does this lamp point to?", + "render": "This lamp points towards {light:direction}" + }, + "lamp_mount": { + "mappings": { + "0": { + "then": "This lamp sits atop of a straight mast" + }, + "1": { + "then": "This lamp sits at the end of a bent mast" + } + }, + "question": "How is this lamp mounted to the pole?" + }, + "lit": { + "mappings": { + "0": { + "then": "This lamp is lit at night" + }, + "1": { + "then": "This lamp is lit 24/7" + }, + "2": { + "then": "This lamp is lit based on motion" + }, + "3": { + "then": "This lamp is lit based on demand (e.g. with a pushbutton)" + } + }, + "question": "When is this lamp lit?" + }, + "method": { + "mappings": { + "0": { + "then": "This lamp is lit electrically" + }, + "1": { + "then": "This lamp uses LEDs" + }, + "2": { + "then": "This lamp uses incandescent lighting" + }, + "3": { + "then": "This lamp uses halogen lighting" + }, + "4": { + "then": "This lamp uses discharge lamps (unknown type)" + }, + "5": { + "then": "This lamp uses a mercury-vapour lamp (lightly blueish)" + }, + "6": { + "then": "This lamp uses metal-halide lamps (bright white)" + }, + "7": { + "then": "This lamp uses fluorescent lighting" + }, + "8": { + "then": "This lamp uses sodium lamps (unknown type)" + }, + "9": { + "then": "This lamp uses low pressure sodium lamps (monochrome orange)" + }, + "10": { + "then": "This lamp uses high pressure sodium lamps (orange with white)" + }, + "11": { + "then": "This lamp is lit using gas" + } + }, + "question": "What kind of lighting does this lamp use?" + }, + "ref": { + "question": "What is the reference number of this street lamp?", + "render": "This street lamp has the reference number {ref}" + }, + "support": { + "mappings": { + "0": { + "then": "This lamp is suspended using cables" + }, + "1": { + "then": "This lamp is mounted on a ceiling" + }, + "2": { + "then": "This lamp is mounted in the ground" + }, + "3": { + "then": "This lamp is mounted on a short pole (mostly < 1.5m)" + }, + "4": { + "then": "This lamp is mounted on a pole" + }, + "5": { + "then": "This lamp is mounted directly to the wall" + }, + "6": { + "then": "This lamp is mounted to the wall using a metal bar" + } + }, + "question": "How is this street lamp mounted?" + } + }, + "title": { + "mappings": { + "0": { + "then": "Street Lamp {ref}" + } + }, + "render": "Street Lamp" + } + }, "surveillance_camera": { "name": "Surveillance camera's", "tagRenderings": { @@ -3375,6 +3530,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 164cb9209..90d41cf41 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 71e4d058c..48c8dda42 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -745,7 +745,25 @@ "render": "Oggetto relativo alle bici" } }, + "charging_station": { + "tagRenderings": { + "Network": { + "question": "A quale rete appartiene questa stazione di ricarica?", + "render": "{network}" + }, + "OH": { + "question": "Quali sono gli orari di apertura di questa 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 7a35bef11..a3baa6845 100644 --- a/langs/layers/ja.json +++ b/langs/layers/ja.json @@ -72,6 +72,17 @@ "render": "アートワーク" } }, + "charging_station": { + "tagRenderings": { + "Network": { + "question": "この充電ステーションの運営チェーンはどこですか?", + "render": "{network}" + }, + "OH": { + "question": "この充電ステーションはいつオープンしますか?" + } + } + }, "food": { "tagRenderings": { "friture-take-your-container": { diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json index b2cfbfb42..e66382ae2 100644 --- a/langs/layers/nb_NO.json +++ b/langs/layers/nb_NO.json @@ -175,6 +175,16 @@ } } }, + "charging_station": { + "tagRenderings": { + "Network": { + "render": "{network}" + }, + "OH": { + "question": "Når åpnet denne ladestasjonen?" + } + } + }, "ghost_bike": { "name": "Spøkelsessykler", "title": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index fe237824a..0c2a569f7 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": { @@ -3306,6 +3313,154 @@ "render": "Sportterrein" } }, + "street_lamps": { + "name": "Straatlantaarns", + "presets": { + "0": { + "title": "straatlantaarn" + } + }, + "tagRenderings": { + "colour": { + "mappings": { + "0": { + "then": "Deze lantaarn geeft wit licht" + }, + "1": { + "then": "Deze lantaarn geeft groen licht" + }, + "2": { + "then": "Deze lantaarn geeft oranje licht" + } + }, + "question": "Wat voor kleur licht geeft deze lantaarn?", + "render": "Deze lantaarn geeft {light:colour} licht" + }, + "count": { + "mappings": { + "0": { + "then": "Deze lantaarn heeft 1 lamp" + }, + "1": { + "then": "Deze lantaarn heeft 2 lampen" + } + }, + "question": "Hoeveel lampen heeft deze lantaarn?", + "render": "Deze lantaarn heeft {light:count} lampen" + }, + "direction": { + "question": "Waar is deze lamp heengericht?", + "render": "Deze lantaarn is gericht naar {light:direction}" + }, + "lamp_mount": { + "mappings": { + "0": { + "then": "Deze lantaarn zit boven op een rechte paal" + }, + "1": { + "then": "Deze lantaarn zit aan het eind van een gebogen paal" + } + }, + "question": "Hoe zit deze lantaarn aan de paal?" + }, + "lit": { + "mappings": { + "0": { + "then": "Deze lantaarn is 's nachts verlicht" + }, + "1": { + "then": "Deze lantaarn is 24/7 verlicht" + }, + "2": { + "then": "Deze lantaarn is verlicht op basis van beweging" + }, + "3": { + "then": "Deze lantaarn is verlicht op verzoek (bijv. met een drukknop)" + } + }, + "question": "Wanneer is deze lantaarn verlicht?" + }, + "method": { + "mappings": { + "0": { + "then": "Deze lantaarn is elektrisch verlicht" + }, + "1": { + "then": "Deze lantaarn gebruikt LEDs" + }, + "2": { + "then": "Deze lantaarn gebruikt gloeilampen" + }, + "3": { + "then": "Deze lantaarn gebruikt halogeen verlichting" + }, + "4": { + "then": "Deze lantaarn gebruikt gasontladingslampen (onbekend type)" + }, + "5": { + "then": "Deze lantaarn gebruikt een kwiklamp (enigszins blauwachtig)" + }, + "6": { + "then": "Deze lantaarn gebruikt metaalhalidelampen" + }, + "7": { + "then": "Deze lantaarn gebruikt fluorescentieverlichting (TL en spaarlamp)" + }, + "8": { + "then": "Deze lantaarn gebruikt natriumlampen (onbekend type)" + }, + "9": { + "then": "Deze lantaarn gebruikt lagedruknatriumlampen (monochroom oranje)" + }, + "10": { + "then": "Deze lantaarn gebruikt hogedruknatriumlampen (oranje met wit)" + }, + "11": { + "then": "Deze lantaarn wordt verlicht met gas" + } + }, + "question": "Wat voor verlichting gebruikt deze lantaarn?" + }, + "ref": { + "question": "Wat is het nummer van deze straatlantaarn?", + "render": "Deze straatlantaarn heeft het nummer {ref}" + }, + "support": { + "mappings": { + "0": { + "then": "Deze lantaarn hangt aan kabels" + }, + "1": { + "then": "Deze lantaarn hangt aan een plafond" + }, + "2": { + "then": "Deze lantaarn zit in de grond" + }, + "3": { + "then": "Deze lantaarn zit op een korte paal (meestal < 1.5m)" + }, + "4": { + "then": "Deze lantaarn zit op een paal" + }, + "5": { + "then": "Deze lantaarn hangt direct aan de muur" + }, + "6": { + "then": "Deze lantaarn hangt aan de muur met een metalen balk" + } + }, + "question": "Hoe is deze straatlantaarn gemonteerd?" + } + }, + "title": { + "mappings": { + "0": { + "then": "Straatlantaarn {ref}" + } + }, + "render": "Straatlantaarn" + } + }, "surveillance_camera": { "name": "Bewakingscamera's", "tagRenderings": { @@ -3814,6 +3969,17 @@ } } }, + "mapRendering": { + "0": { + "iconSize": { + "mappings": { + "0": { + "then": "Vuilnisbak" + } + } + } + } + }, "name": "Vuilnisbak", "presets": { "0": { @@ -3827,10 +3993,10 @@ "then": "Deze vuilnisbak heeft een verdeler voor hondenpoepzakjes" }, "1": { - "then": "Deze vuilbak heeft geen verdeler voor hondenpoepzakjes" + "then": "Deze vuilnisbak heeft geenverdeler voor hondenpoepzakjes" }, "2": { - "then": "Deze vuilnisbak heeft geen verdeler voor hondenpoepzakjes" + "then": "Deze vuilnisbaak heeft waarschijnlijk geen verdeler voor hondenpoepzakjes" } }, "question": "Heeft deze vuilnisbak een verdeler voor hondenpoepzakjes?" diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 0711a616a..8aede7391 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -631,6 +631,57 @@ } } }, + "charging_station": { + "presets": { + "0": { + "title": "Зарядная станция" + } + }, + "tagRenderings": { + "Network": { + "question": "К какой сети относится эта станция?", + "render": "{network}" + }, + "OH": { + "question": "В какое время работает эта зарядная станция?" + } + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": " минут", + "humanSingular": " минута" + }, + "1": { + "human": " часов", + "humanSingular": " час" + }, + "2": { + "human": " дней", + "humanSingular": " день" + } + } + }, + "1": { + "applicableUnits": { + "0": { + "human": "Вольт" + } + } + }, + "3": { + "applicableUnits": { + "0": { + "human": "киловатт" + }, + "1": { + "human": "мегаватт" + } + } + } + } + }, "crossings": { "presets": { "1": { @@ -656,6 +707,13 @@ } }, "defibrillator": { + "icon": { + "mappings": { + "0": { + "then": "./assets/layers/defibrillator/aed_checked.svg" + } + } + }, "name": "Дефибрилляторы", "presets": { "0": { @@ -1383,6 +1441,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 59f59f5aa..951be71a1 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -445,6 +445,17 @@ "render": "單車停車場" } }, + "charging_station": { + "tagRenderings": { + "Network": { + "question": "充電站所屬的網路是?", + "render": "{network}" + }, + "OH": { + "question": "何時是充電站開放使用的時間?" + } + } + }, "ghost_bike": { "name": "幽靈單車", "title": { 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/nl.json b/langs/nl.json index 1f4321e12..8aaad1ad0 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -171,7 +171,7 @@ }, "opening_hours": { "error_loading": "Sorry, deze openingsuren kunnen niet getoond worden", - "open_during_ph": "Op een feestdag is deze zaak", + "open_during_ph": "Op een feestdag is dit", "opensAt": "vanaf", "openTill": "tot", "closed_until": "Gesloten - open op {date}", @@ -181,7 +181,7 @@ "ph_closed": "gesloten", "ph_open": "open", "ph_open_as_usual": "geopend zoals gewoonlijk", - "not_all_rules_parsed": "De openingsuren van deze zaak zijn ingewikkeld. De volgende regels worden niet getoond bij het ingeven:", + "not_all_rules_parsed": "De openingsuren zijn ingewikkeld. De volgende regels worden niet getoond bij het ingeven:", "loadingCountry": "Het land wordt nog bepaald…" }, "skippedQuestions": "Enkele vragen werden overgeslaan", @@ -313,4 +313,4 @@ "multi_apply": { "autoApply": "Wijzigingen aan eigenschappen {attr_names} zullen ook worden uitgevoerd op {count} andere objecten." } -} +} \ No newline at end of file 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/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/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 8915bde92..93b3b2ac1 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" } }, @@ -662,6 +662,7 @@ "title": "Open Etymology Map" }, "facadegardens": { + "description": "Fassadengärten, grüne Fassaden und Bäume in der Stadt bringen nicht nur Ruhe und Frieden, sondern auch eine schönere Stadt, eine größere Artenvielfalt, einen Kühleffekt und eine bessere Luftqualität.
    Klimaan VZW und Mechelen Klimaatneutraal wollen bestehende und neue Fassadengärten als Beispiel für Menschen, die ihren eigenen Garten anlegen wollen, oder für naturverbundene Stadtspaziergänger kartieren.
    Mehr Informationen über das Projekt unter klimaan.be.", "layers": { "0": { "description": "Fassadengärten", @@ -745,8 +746,7 @@ } }, "shortDescription": "Diese Karte zeigt Fassadengärten mit Bildern und Details zu Ausrichtung, Sonneneinstrahlung und Pflanzen.", - "title": "Fassadengärten", - "description": "Fassadengärten, grüne Fassaden und Bäume in der Stadt bringen nicht nur Ruhe und Frieden, sondern auch eine schönere Stadt, eine größere Artenvielfalt, einen Kühleffekt und eine bessere Luftqualität.
    Klimaan VZW und Mechelen Klimaatneutraal wollen bestehende und neue Fassadengärten als Beispiel für Menschen, die ihren eigenen Garten anlegen wollen, oder für naturverbundene Stadtspaziergänger kartieren.
    Mehr Informationen über das Projekt unter klimaan.be." + "title": "Fassadengärten" }, "food": { "title": "Restaurants und Schnellimbisse" @@ -769,13 +769,6 @@ "layers": { "0": { "description": "Hackerspace", - "icon": { - "mappings": { - "0": { - "then": "./assets/themes/hackerspaces/led.png" - } - } - }, "name": "Hackerspace", "presets": { "0": { @@ -842,7 +835,7 @@ "title": "Hackerspaces" }, "hailhydrant": { - "description": "Auf dieser Karte können Sie Hydranten, Feuerwachen, Krankenwagen und Feuerlöscher in Ihren bevorzugten Stadtvierteln finden und aktualisieren.\n\nSie können Ihren genauen Standort verfolgen (nur mobil) und in der unteren linken Ecke die für Sie relevanten Ebenen auswählen. Sie können mit diesem Tool auch Pins (Points of Interest) zur Karte hinzufügen oder bearbeiten und durch die Beantwortung verfügbarer Fragen zusätzliche Angaben machen.\n\nAlle von Ihnen vorgenommenen Änderungen werden automatisch in der globalen Datenbank von OpenStreetMap gespeichert und können von anderen frei weiterverwendet werden.", + "description": "Auf dieser Karte können Sie Hydranten, Feuerwachen, Krankenwagen und Feuerlöscher in Ihren bevorzugten Stadtvierteln finden und aktualisieren. \n\nSie können Ihren genauen Standort verfolgen (nur mobil) und in der unteren linken Ecke die für Sie relevanten Ebenen auswählen. Sie können mit diesem Tool auch Pins (Points of Interest) zur Karte hinzufügen oder bearbeiten und durch die Beantwortung verfügbarer Fragen zusätzliche Angaben machen. \n\nAlle von Ihnen vorgenommenen Änderungen werden automatisch in der globalen Datenbank von OpenStreetMap gespeichert und können von anderen frei weiterverwendet werden.", "layers": { "0": { "description": "Kartenebene zur Anzeige von Hydranten.", @@ -949,13 +942,6 @@ "tagRenderings": { "station-name": { "question": "Wie lautet der Name dieser Feuerwache?" - }, - "station-agency": { - "mappings": { - "0": { - "then": "Brandschutzbehörde" - } - } } }, "title": { @@ -965,28 +951,22 @@ "3": { "presets": { "0": { - "description": "Eine Rettungsstation der Karte hinzufügen", - "title": "Rettungswache" + "description": "Eine Rettungsstation der Karte hinzufügen" } - }, - "description": "Eine Rettungswache ist ein Ort, an dem Rettungsfahrzeuge, medizinische Ausrüstung, persönliche Schutzausrüstung und anderes medizinisches Material untergebracht sind.", - "name": "Karte der Rettungswachen", - "title": { - "render": "Rettungswache" } } }, "shortDescription": "Hydranten, Feuerlöscher, Feuerwachen und Rettungswachen." }, "maps": { - "title": "Eine Karte der Karten", - "shortDescription": "Eine Karte mit allen (touristischen) Karten, die OpenStreetMap kennt", - "description": "Auf dieser Karte findest du alle Karten, die OpenStreetMap kennt - typischerweise eine große Karte auf einer Informationstafel, die das Gebiet, die Stadt oder die Region zeigt, z.B. eine touristische Karte auf der Rückseite einer Plakatwand, eine Karte eines Naturschutzgebietes, eine Karte der Radwegenetze in der Region, ...)

    Wenn eine Karte fehlt, können Sie diese leicht auf OpenStreetMap kartieren." + "description": "Auf dieser Karte findest du alle Karten, die OpenStreetMap kennt - typischerweise eine große Karte auf einer Informationstafel, die das Gebiet, die Stadt oder die Region zeigt, z.B. eine touristische Karte auf der Rückseite einer Plakatwand, eine Karte eines Naturschutzgebietes, eine Karte der Radwegenetze in der Region, ...)

    Wenn eine Karte fehlt, können Sie diese leicht auf OpenStreetMap kartieren.", + "shortDescription": "Dieses Thema zeigt alle (touristischen) Karten, die OpenStreetMap kennt", + "title": "Eine Karte der Karten" }, "natuurpunt": { + "description": "Auf dieser Karte können Sie alle Naturschutzgebiete von Natuurpunt finden ", "shortDescription": "Diese Karte zeigt Naturschutzgebiete des flämischen Naturverbands Natuurpunt", - "title": "Naturschutzgebiete", - "description": "Auf dieser Karte können Sie alle Naturschutzgebiete von Natuurpunt finden " + "title": "Naturschutzgebiete" }, "observation_towers": { "description": "Öffentlich zugänglicher Aussichtsturm", @@ -1033,7 +1013,7 @@ } } }, - "title": "Freie Windenergie-Karte" + "title": "OpenWindPowerMap" }, "parkings": { "description": "Diese Karte zeigt Parkplätze", @@ -1041,13 +1021,13 @@ "title": "Parken" }, "personal": { - "description": "Erstellen Sie ein persönliches Thema, das auf allen verfügbaren Ebenen aller Themen basiert. Um Daten anzuzeigen, öffnen Sie die Ebenenauswahl", + "description": "Erstellen Sie ein persönliches Thema auf der Grundlage aller verfügbaren Ebenen aller Themen", "title": "Persönliches Thema" }, "playgrounds": { + "description": "Auf dieser Karte finden Sie Spielplätze und können weitere Informationen hinzufügen", "shortDescription": "Eine Karte mit Spielplätzen", - "title": "Spielplätze", - "description": "Auf dieser Karte finden Sie Spielplätze und können weitere Informationen hinzufügen" + "title": "Spielpläzte" }, "postboxes": { "layers": { @@ -1098,9 +1078,9 @@ "title": "Karte mit Briefkästen und Poststellen" }, "shops": { + "description": "Auf dieser Karte kann man grundlegende Informationen über Geschäfte markieren, Öffnungszeiten und Telefonnummern hinzufügen", "shortDescription": "Eine bearbeitbare Karte mit grundlegenden Geschäftsinformationen", - "title": "Freie Geschäftskarte", - "description": "Auf dieser Karte kann man grundlegende Informationen über Geschäfte markieren, Öffnungszeiten und Telefonnummern hinzufügen" + "title": "Freie Geschäftskarte" }, "sport_pitches": { "description": "Ein Sportplatz ist eine Fläche, auf der Sportarten gespielt werden", @@ -1124,7 +1104,7 @@ "uk_addresses": { "description": "Tragen Sie zu OpenStreetMap bei, indem Sie Adressinformationen ausfüllen", "layers": { - "1": { + "2": { "description": "Adressen", "name": "Bekannte Adressen in OSM", "tagRenderings": { diff --git a/langs/themes/en.json b/langs/themes/en.json index 3e5c386cf..ff3351ae1 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -610,7 +610,7 @@ "title": "Bicycle infrastructure" }, "cyclestreets": { - "description": "A cyclestreet is a street where motorized traffic is not allowed to overtake cyclists. They are signposted by a special traffic sign. Cyclestreets can be found in the Netherlands and Belgium, but also in Germany and France. ", + "description": "A cyclestreet is is a street where motorized traffic is not allowed to overtake cyclists. They are signposted by a special traffic sign. Cyclestreets can be found in the Netherlands and Belgium, but also in Germany and France. ", "layers": { "0": { "description": "A cyclestreet is a street where motorized traffic is not allowed to overtake a cyclist", @@ -666,14 +666,14 @@ }, "cyclofix": { "description": "The goal of this map is to present cyclists with an easy-to-use solution to find the appropriate infrastructure for their needs.

    You can track your precise location (mobile only) and select layers that are relevant for you in the bottom left corner. You can also use this tool to add or edit pins (points of interest) to the map and provide more data by answering the questions.

    All changes you make will automatically be saved in the global database of OpenStreetMap and can be freely re-used by others.

    For more information about the cyclofix project, go to cyclofix.osm.be.", - "title": "Cyclofix — an open map for cyclists" + "title": "Cyclofix - an open map for cyclists" }, "drinking_water": { "description": "On this map, publicly accessible drinking water spots are shown and can be easily added", "title": "Drinking Water" }, "etymology": { - "description": "On this map, you can see what an object is named after. The streets, buildings, ... come from OpenStreetMap which got linked with Wikidata. In the popup, you'll see the Wikipedia article (if it exists) or a Wikidata box of what the object is named after. If the object itself has a Wikipedia page, that'll be shown too.

    You can help contribute too!Zoom in enough and all streets will show up. You can click one and a Wikidata-search box will popup. With a few clicks, you can add an etymology link. Note that you need a free OpenStreetMap account to do this.", + "description": "On this map, you can see what an object is named after. The streets, buildings, ... come from OpenStreetMap which got linked with Wikidata. In the popup, you'll see the Wikipedia article (if it exists) or a wikidata box of what the object is named after. If the object itself has a wikipedia page, that'll be shown too.

    You can help contribute too!Zoom in enough and all streets will show up. You can click one and a Wikidata-search box will popup. With a few clicks, you can add an etymology link. Note that you need a free OpenStreetMap account to do this.", "layers": { "1": { "override": { @@ -708,7 +708,7 @@ }, "facadegardens-direction": { "question": "What is the orientation of the garden?", - "render": "Orientation: {direction} (where 0=N and 90=E)" + "render": "Orientation: {direction} (where 0=N and 90=O)" }, "facadegardens-edible": { "mappings": { @@ -792,18 +792,23 @@ "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.", + "layers": { + "1": { + "tagRenderings": { + "building type": { + "question": "What kind of building is this?" + } + } + } + } + }, "hackerspaces": { "description": "On this map you can see hackerspaces, add a new hackerspace or update data directly", "layers": { "0": { "description": "Hackerspace", - "icon": { - "mappings": { - "0": { - "then": "./assets/themes/hackerspaces/led.png" - } - } - }, "name": "Hackerspace", "presets": { "0": { @@ -1226,11 +1231,86 @@ "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", "title": "Sport pitches" }, + "street_lighting": { + "description": "On this map you can find everything about street lighting", + "layers": { + "1": { + "name": "Lit streets", + "tagRenderings": { + "lit": { + "mappings": { + "0": { + "then": "This street is lit" + }, + "1": { + "then": "This street is not lit" + }, + "2": { + "then": "This street is lit at night" + }, + "3": { + "then": "This street is lit 24/7" + } + }, + "question": "Is this street lit?" + } + }, + "title": { + "render": "Lit street" + } + }, + "2": { + "name": "All streets", + "tagRenderings": { + "lit": { + "mappings": { + "0": { + "then": "This street is lit" + }, + "1": { + "then": "This street is not lit" + }, + "2": { + "then": "This street is lit at night" + }, + "3": { + "then": "This street is lit 24/7" + } + }, + "question": "Is this street lit?" + } + }, + "title": { + "render": "Street" + } + } + }, + "title": "Street Lighting" + }, "surveillance": { "description": "On this open map, you can find surveillance cameras.", "shortDescription": "Surveillance cameras and other means of surveillance", @@ -1248,7 +1328,7 @@ "uk_addresses": { "description": "Contribute to OpenStreetMap by filling out address information", "layers": { - "1": { + "2": { "description": "Addresses", "name": "Known addresses in OSM", "tagRenderings": { diff --git a/langs/themes/eo.json b/langs/themes/eo.json index e598b007f..4912597b3 100644 --- a/langs/themes/eo.json +++ b/langs/themes/eo.json @@ -39,81 +39,7 @@ "ghostbikes": { "title": "Fantombicikloj" }, - "hackerspaces": { - "layers": { - "0": { - "icon": { - "mappings": { - "0": { - "then": "./assets/themes/hackerspaces/led.png" - } - } - }, - "tagRenderings": { - "hackerspaces-opening_hours": { - "render": "{opening_hours_table()}" - } - }, - "title": { - "mappings": { - "0": { - "then": " {name}" - } - } - } - } - } - }, - "hailhydrant": { - "layers": { - "1": { - "tagRenderings": { - "extinguisher-location": { - "render": "Loko: {location}" - } - } - } - } - }, "maps": { "title": "Mapo de mapoj" - }, - "openwindpowermap": { - "layers": { - "0": { - "title": { - "mappings": { - "0": { - "then": "{name}" - } - } - }, - "units": { - "0": { - "applicableUnits": { - "0": { - "human": " megavatoj" - }, - "1": { - "human": " kilovatoj" - }, - "2": { - "human": " vatoj" - }, - "3": { - "human": " gigavatoj" - } - } - }, - "1": { - "applicableUnits": { - "0": { - "human": " metro" - } - } - } - } - } - } } } \ No newline at end of file diff --git a/langs/themes/it.json b/langs/themes/it.json index ba7383d7a..32d90f489 100644 --- a/langs/themes/it.json +++ b/langs/themes/it.json @@ -264,72 +264,8 @@ "description": "In questa cartina puoi trovare vari luoghi per arrampicata come ad esempio palestre di arrampicata, sale di pratica e rocce naturali.", "descriptionTail": "La cartina di arrampicata è stata originariamente creata da Christian Neumann. Si prega di scrivere qua se si hanno commenti o domande da fare.

    Il progetto usa i dati del progetto OpenStreetMap.

    ", "layers": { - "0": { - "description": "Un club o associazione di arrampacata", - "name": "Club di arrampicata", - "presets": { - "0": { - "description": "Un club di arrampicata", - "title": "Club di arrampicata" - }, - "1": { - "description": "Un’associazione che ha a che fare con l’arrampicata", - "title": "Associazione di arrampicata" - } - }, - "tagRenderings": { - "climbing_club-name": { - "question": "Qual è il nome di questo club o associazione di arrampicata?", - "render": "{name}" - } - }, - "title": { - "mappings": { - "0": { - "then": "Associazione di arrampicata" - } - }, - "render": "Club di arrampicata" - } - }, - "1": { - "description": "Una palestra di arrampicata", - "name": "Palestre di arrampicata", - "tagRenderings": { - "name": { - "question": "Qual è il nome di questa palestra di arrampicata?", - "render": "{name}" - } - }, - "title": { - "mappings": { - "0": { - "then": "Palestra di arrampicata {name}" - } - }, - "render": "Palestra di arrampicata" - } - }, "2": { - "name": "Vie di arrampicata", - "presets": { - "0": { - "title": "Via di arrampicata" - } - }, "tagRenderings": { - "Bolts": { - "mappings": { - "0": { - "then": "In questo percorso non sono presenti bulloni" - }, - "1": { - "then": "In questo percorso non sono presenti bulloni" - } - }, - "question": "Quanti bulloni sono presenti in questo percorso prima di arrivare alla moulinette?", - "render": "Questo percorso ha {climbing:bolts} bulloni" - }, "Difficulty": { "question": "Qual è la difficoltà di questa via di arrampicata nel sistema francese/belga?", "render": "Il grado di difficoltà è {climbing:grade:french} nel sistema francese/belga" @@ -346,9 +282,6 @@ }, "question": "Come si chiama questa via di arrampicata?", "render": "{name}" - }, - "Rock type": { - "render": "Il tipo di roccia è {_embedding_features_with_rock:rock} come dichiarato sul muro circostante" } }, "title": { @@ -359,249 +292,12 @@ }, "render": "Via di arrampicata" } - }, - "3": { - "description": "Un’opportunità di arrampicata", - "name": "Opportunità di arrampicata", - "presets": { - "0": { - "description": "Un’opportunità di arrampicata", - "title": "Opportunità di arrampicata" - } - }, - "tagRenderings": { - "Containe {_contained_climbing_routes_count} routes": { - "render": "

    Contiene {_contained_climbing_routes_count} vie

      {_contained_climbing_routes}
    " - }, - "Contained routes hist": { - "render": "

    Riassunto delle difficoltà

    {histogram(_difficulty_hist)}" - }, - "Contained routes length hist": { - "render": "

    Riassunto della lunghezza

    {histogram(_length_hist)}" - }, - "Rock type (crag/rock/cliff only)": { - "mappings": { - "0": { - "then": "Calcare" - } - }, - "question": "Qual è il tipo di roccia qua?", - "render": "Il tipo di roccia è {rock}" - }, - "Type": { - "mappings": { - "0": { - "then": "Un masso per arrampicata (una singola roccia o falesia con una o poche vie di arrampicata che possono essere scalate in sicurezza senza una corda)" - }, - "1": { - "then": "Un muro da arrampicata (un singolo masso o falesia con almeno qualche via per arrampicata)" - } - } - }, - "name": { - "mappings": { - "0": { - "then": "Questa opportunità di arrampicata non ha un nome" - } - }, - "question": "Qual è il nome di questa opportunità di arrampicata?", - "render": "{name}" - } - }, - "title": { - "mappings": { - "0": { - "then": "Muro da arrampicata {name}" - }, - "1": { - "then": "Area di arrampicata {name}" - }, - "2": { - "then": "Sito di arrampicata" - }, - "3": { - "then": "Opportunità di arrampicata {name}" - } - }, - "render": "Opportunità di arrampicata" - } - }, - "4": { - "description": "Un’opportunità di arrampicata?", - "name": "Opportunità di arrampicata?", - "tagRenderings": { - "climbing-opportunity-name": { - "render": "{name}" - }, - "climbing-possible": { - "mappings": { - "0": { - "then": "Non è possibile arrampicarsi qua" - }, - "1": { - "then": "È possibile arrampicarsi qua" - }, - "2": { - "then": "Non è possibile arrampicarsi qua" - } - }, - "question": "È possibile arrampicarsi qua?" - } - }, - "title": { - "render": "Opportunità di arrampicata?" - } - } - }, - "overrideAll": { - "tagRenderings+": { - "0": { - "question": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?" - }, - "1": { - "mappings": { - "0": { - "then": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile
    {_embedding_feature:access:description}" - }, - "1": { - "then": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi
    {_embedding_feature:access:description}" - }, - "2": { - "then": "L’ elemento che lo contiene indica che è accessibile solo ai clienti
    {_embedding_feature:access:description}" - }, - "3": { - "then": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club
    {_embedding_feature:access:description}" - } - } - }, - "2": { - "mappings": { - "0": { - "then": "Pubblicamente accessibile a chiunque" - }, - "1": { - "then": "È necessario avere un’autorizzazione per entrare" - }, - "2": { - "then": "Riservato ai clienti" - }, - "3": { - "then": "Riservato ai membri del club" - } - }, - "question": "Chi può accedervi?" - }, - "4": { - "question": "Quale è la lunghezza (media) delle vie in metri?", - "render": "Le vie sono lunghe mediamente {canonical(climbing:length)}" - }, - "5": { - "question": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?", - "render": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga" - }, - "6": { - "question": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?", - "render": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga" - }, - "7": { - "mappings": { - "0": { - "then": "L’arrampicata su massi è possibile qua" - }, - "1": { - "then": "L’arrampicata su massi non è possibile qua" - }, - "2": { - "then": "L’arrampicata su massi è possibile anche se su poche vie" - }, - "3": { - "then": "Sono presenti {climbing:boulder} vie di arrampicata su massi" - } - }, - "question": "È possibile praticare ‘bouldering’ qua?" - }, - "8": { - "mappings": { - "0": { - "then": "È possibile arrampicarsi con moulinette qua" - }, - "1": { - "then": "Non è possibile arrampicarsi con moulinette qua" - }, - "2": { - "then": "Sono presenti {climbing:toprope} vie con moulinette" - } - }, - "question": "È possibile arrampicarsi con la corda dall’alto qua?" - }, - "9": { - "mappings": { - "0": { - "then": "L’arrampicata sportiva è possibile qua" - }, - "1": { - "then": "L’arrampicata sportiva non è possibile qua" - }, - "2": { - "then": "Sono presenti {climbing:sport} vie di arrampicata sportiva" - } - }, - "question": "È possibile arrampicarsi qua con ancoraggi fissi?" - }, - "10": { - "mappings": { - "0": { - "then": "L’arrampicata tradizionale è possibile qua" - }, - "1": { - "then": "L’arrampicata tradizionale non è possibile qua" - }, - "2": { - "then": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale" - } - }, - "question": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?" - }, - "11": { - "mappings": { - "0": { - "then": "È presente una parete per l’arrampicata di velocità" - }, - "1": { - "then": "Non è presente una parete per l’arrampicata di velocità" - }, - "2": { - "then": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità" - } - }, - "question": "È presente una prete per l’arrampicata di velocità?" - } - }, - "units+": { - "0": { - "applicableUnits": { - "0": { - "human": " metri" - }, - "1": { - "human": " piedi" - } - } - } } }, "title": "Mappa aperta per le arrampicate" }, "cycle_highways": { "description": "Questa cartina mostra le strade per velocipedi", - "layers": { - "0": { - "name": "strade per velocipedi", - "title": { - "render": "strada per velocipedi" - } - } - }, "title": "Strade per velocipedi" }, "cycle_infra": { @@ -640,20 +336,10 @@ "tagRenderings+": { "0": { "mappings": { - "0": { - "then": "Questa è una strada ciclabile (e ha un limite di velocità massima di 30 km/h)" - }, - "1": { - "then": "Questa è una strada ciclabile" - }, - "2": { - "then": "Diverrà tra poco una strada ciclabile" - }, "3": { "then": "Questa strada non è una strada ciclabile" } - }, - "question": "È una strada ciclabile?" + } }, "1": { "question": "Questa strada diventerà una strada ciclabile quando?", @@ -666,7 +352,7 @@ }, "cyclofix": { "description": "Questa mappa offre a chi va in bici una soluzione semplice per trovare tutte le infrastrutture di cui ha bisogno.

    Puoi tracciare la tua posizione esatta (solo su mobile) e selezionare i livelli che ti interessano nell'angolo in basso a sinistra. Puoi anche usare questo strumento per aggiungere o modificare punti di interesse alla mappa e aggiungere nuove informazioni rispendendo alle domande.

    Tutte le modifiche che apporterai saranno automaticamente salvate nel database mondiale di OpenStreetMap e potranno essere liberamente riutilizzate da tutti e tutte.

    Per maggiori informazioni sul progetto ciclofix, visita cyclofix.osm.be.", - "title": "Cyclofix — una mappa libera per chi va in bici" + "title": "Cyclofix - una mappa libera per chi va in bici" }, "drinking_water": { "description": "Questa mappa mostra tutti i luoghi in cui è disponibile acqua potabile ed è possibile aggiungerne di nuovi", @@ -693,8 +379,6 @@ "description": "I giardini veritcali e gli alberi in città non solo portano pace e tranquillità ma creano anche un ambiente più bello, aumentano la biodiversità, rendono il clima più fresco e migliorano la qualità dell’aria.
    Klimaan VZW e Mechelen Klimaatneutraal vogliono mappare sia i giardini verticali esistenti che quelli nuovi per mostrarli a quanti vogliono costruire un loro proprio giardino o per quelli che amano la natura e vogliono camminare per la città.
    Per ulteriori informazioni visita klimaan.be.", "layers": { "0": { - "description": "Giardini verticali", - "name": "Giardini verticali", "presets": { "0": { "description": "Aggiungi un giardino verticale", @@ -706,10 +390,6 @@ "question": "Altre informazioni per descrivere il giardino (se necessarie e non riportate qui sopra)", "render": "Maggiori dettagli: {description}" }, - "facadegardens-direction": { - "question": "Com’è orientato questo giardino?", - "render": "Orientamento: {direction} (0 per il Nord e 90 per l’Est)" - }, "facadegardens-edible": { "mappings": { "0": { @@ -746,8 +426,7 @@ "1": { "then": "Non c'è un contenitore per raccogliere la pioggia" } - }, - "question": "È stata installata una riserva d’acqua per il giardino?" + } }, "facadegardens-start_date": { "question": "Quando è stato realizzato il giardino? (è sufficiente l'anno)", @@ -767,9 +446,6 @@ }, "question": "Il giardino è al sole o in ombra?" } - }, - "title": { - "render": "Giardino verticale" } } }, @@ -794,78 +470,6 @@ }, "hackerspaces": { "description": "Su questa cartina è possibile vedere gli hackerspace, aggiungerne di nuovi o aggiornare le informazioni tutto in maniera pratica", - "layers": { - "0": { - "description": "Hackerspace", - "icon": { - "mappings": { - "0": { - "then": "./assets/themes/hackerspaces/led.png" - } - } - }, - "name": "Hackerspace", - "presets": { - "0": { - "description": "Un hackerspace è un’area dove si ritrovano le persone interessate al software", - "title": "Hackerspace" - }, - "1": { - "description": "Un makerspace è un luogo dove gli amanti del fai-da-te si ritrovano per sperimentare con dispositivi di elettronica come arduino, strisce LED, etc.", - "title": "Makerspace" - } - }, - "tagRenderings": { - "hackerspaces-name": { - "question": "Qual è il nome di questo hackerspace?", - "render": "Questo hackerspace si chiama {name}" - }, - "hackerspaces-opening_hours": { - "mappings": { - "0": { - "then": "Aperto sempre" - } - }, - "question": "Quando è aperto questo hackerspace?", - "render": "{opening_hours_table()}" - }, - "hackerspaces-start_date": { - "question": "Quando è stato creato questo hackerspace?", - "render": "Questo hackerspace è stato creato il {start_date}" - }, - "hs-club-mate": { - "mappings": { - "0": { - "then": "In questo hackerspace viene servito Club-Mate" - }, - "1": { - "then": "In questo hackerspace non viene servito Club-Mate" - } - }, - "question": "In questo hackerspace si serve Club-Mate?" - }, - "is_makerspace": { - "mappings": { - "0": { - "then": "Si tratta di un makerspace" - }, - "1": { - "then": "Si tratta di un hackerspace tradizionale (orientato al software)" - } - }, - "question": "È un hackerspace o un makerspace?" - } - }, - "title": { - "mappings": { - "0": { - "then": " {name}" - } - }, - "render": "Hackerspace" - } - } - }, "shortDescription": "Una cartina degli hackerspace", "title": "Hackerspace" }, @@ -873,200 +477,40 @@ "description": "In questa cartina puoi vedere e aggiornare idranti, stazioni dei pompieri, stazioni delle ambulanze ed estintori del tuo quartiere preferito.\n\nPuoi seguire la tua posizione precisa (solo su cellulare) e selezionare i livelli che ti interessano nell’angolo in basso a sinistra. Puoi anche usare questo strumento per aggiungere o modificare i PDI sulla mappa e fornire ulteriori dettagli rispondendo alle domande.\n\nTutte le modifiche che farai verranno automaticamente salvate nel database globale di OpenStreetMap e potranno essere riutilizzate liberamente da tutti.", "layers": { "0": { - "description": "Livello della mappa che mostra gli idranti antincendio.", - "name": "Mappa degli idranti", - "presets": { - "0": { - "description": "Un idrante è un punto di collegamento dove i pompieri possono estrarre acqua. Potrebbe trovarsi sottoterra.", - "title": "Idrante antincendio" - } - }, "tagRenderings": { "hydrant-color": { "mappings": { - "0": { - "then": "Il colore dell’idrante è sconosciuto." - }, - "1": { - "then": "Il colore dell’idrante è giallo." - }, "2": { "then": "L'idrante è rosso." } - }, - "question": "Qual è il colore dell’idrante?", - "render": "Il colore dell’idrante è {colour}" - }, - "hydrant-state": { - "mappings": { - "0": { - "then": "L’idrante è (parzialmente o completamente) funzionante." - }, - "1": { - "then": "L’idrante è fuori servizio." - }, - "2": { - "then": "L’idrante è stato rimosso." - } - }, - "question": "Aggiorna lo stato di funzionamento dell’idrante.", - "render": "Stato di funzionamento" + } }, "hydrant-type": { "mappings": { "0": { "then": "Il tipo di idrante è sconosciuto." - }, - "1": { - "then": " Soprasuolo." - }, - "2": { - "then": " Tubo." - }, - "3": { - "then": " A muro." - }, - "4": { - "then": " Sottosuolo." } }, "question": "Di che tipo è questo idrante?", "render": " Tipo di idrante: {fire_hydrant:type}" } - }, - "title": { - "render": "Idrante" - } - }, - "1": { - "description": "Livello della mappa che mostra gli idranti antincendio.", - "name": "Cartina degli estintori.", - "presets": { - "0": { - "description": "Un estintore è un dispositivo portatile di piccole dimensioni usato per spegnere un incendio", - "title": "Estintore" - } - }, - "tagRenderings": { - "extinguisher-location": { - "mappings": { - "0": { - "then": "Si trova all’interno." - }, - "1": { - "then": "Si trova all’esterno." - } - }, - "question": "Dove è posizionato?", - "render": "Posizione: {location}" - } - }, - "title": { - "render": "Estintori" } }, "2": { "description": "Livello che mostra le caserme dei vigili del fuoco.", "name": "Mappa delle caserme dei vigili del fuoco", - "presets": { - "0": { - "description": "Una caserma dei pompieri è un luogo dove si trovano i mezzi antincendio e i pompieri tra una missione e l’altra.", - "title": "Caserma dei vigili del fuoco" - } - }, "tagRenderings": { - "station-agency": { - "mappings": { - "0": { - "then": "Servizio antincendio governativo" - } - }, - "question": "Quale agenzia gestisce questa stazione?", - "render": "Questa stazione è gestita da {operator}." - }, "station-name": { "question": "Come si chiama questa caserma dei vigili del fuoco?", "render": "Questa caserma si chiama {name}." }, - "station-operator": { - "mappings": { - "0": { - "then": "Questa stazione è gestita dal governo." - }, - "1": { - "then": "Questa stazione è gestita dalla comunità oppure un’associazione informale." - }, - "2": { - "then": "Questa stazione è gestita da un gruppo di volontari ufficiale." - }, - "3": { - "then": "Questa stazione è gestita da privati." - } - }, - "question": "Com’è classificato il gestore di questa stazione?", - "render": "Il gestore è un ente {operator:type}." - }, - "station-place": { - "question": "In che località si trova la stazione? (ad es. quartiere, paese o città)", - "render": "La stazione si trova a {addr:place}." - }, "station-street": { - "question": " Qual è il nome della via in cui si trova la caserma?", - "render": "La stazione si trova in una strada chiamata {addr:street}." + "question": " Qual è il nome della via in cui si trova la caserma?" } }, "title": { "render": "Caserma dei vigili del fuoco" } - }, - "3": { - "description": "La stazione delle ambulanze è un’area per lo stoccaggio delle ambulanze, dell’equipaggiamento medico, dei dispositivi di protezione individuale e di altre forniture medicali.", - "name": "Carta delle stazioni delle ambulanze", - "presets": { - "0": { - "description": "Aggiungi una stazione delle ambulanza alla mappa", - "title": "Stazione delle ambulanze" - } - }, - "tagRenderings": { - "ambulance-agency": { - "question": "Quale agenzia gestisce questa stazione?", - "render": "Questa stazione è gestita da {operator}." - }, - "ambulance-name": { - "question": "Qual è il nome di questa stazione delle ambulanze?", - "render": "Questa stazione è chiamata {name}." - }, - "ambulance-operator-type": { - "mappings": { - "0": { - "then": "La stazione è gestita dal governo." - }, - "1": { - "then": "La stazione è gestita dalla comunità o un’organizzazione non ufficiale." - }, - "2": { - "then": "La stazione è gestita da un gruppo ufficiale di volontari." - }, - "3": { - "then": "La stazione è gestita da un privato." - } - }, - "question": "Com’è classificato il gestore della stazione?", - "render": "L’operatore è un ente {operator:type}." - }, - "ambulance-place": { - "question": "Dove si trova la stazione? (ad es. quartiere, paese o città)", - "render": "La stazione si trova a {addr:place}." - }, - "ambulance-street": { - "question": " Come si chiama la strada in cui si trova questa stazione?", - "render": "Questa stazione si trova in {addr:street}." - } - }, - "title": { - "render": "Stazione delle ambulanze" - } } }, "shortDescription": "Carta che mostra gli idranti, gli estintori, le caserme dei vigili del fuoco e le stazioni delle ambulanze.", @@ -1089,71 +533,6 @@ }, "openwindpowermap": { "description": "Una cartina per la visione e la modifica delle turbine eoliche.", - "layers": { - "0": { - "name": "pala eolica", - "presets": { - "0": { - "title": "pala eolica" - } - }, - "tagRenderings": { - "turbine-diameter": { - "question": "Qual è il diametro (in metri) del rotore di questa pala eolica?", - "render": "Il diametro del rotore di questa pala eolica è di {rotor:diameter} metri." - }, - "turbine-height": { - "question": "Qual è l’altezza (in metri e raggio del rotore incluso) di questa pala eolica?", - "render": "L’altezza totale (raggio del rotore incluso) di questa pala eolica è di {height} metri." - }, - "turbine-operator": { - "question": "Chi gestisce questa pala eolica?", - "render": "Questa pala eolica è gestita da {operator}." - }, - "turbine-output": { - "question": "Quant’è la potenza generata da questa pala eolica? (ad es. 2.3 MW)", - "render": "La potenza generata da questa pala eolica è {generator:output:electricity}." - }, - "turbine-start-date": { - "question": "Quando è entrata in funzione questa pala eolica?", - "render": "Questa pala eolica è entrata in funzione in data {start_date}." - } - }, - "title": { - "mappings": { - "0": { - "then": "{name}" - } - }, - "render": "pala eolica" - }, - "units": { - "0": { - "applicableUnits": { - "0": { - "human": " megawatt" - }, - "1": { - "human": " kilowatt" - }, - "2": { - "human": " watt" - }, - "3": { - "human": " gigawatt" - } - } - }, - "1": { - "applicableUnits": { - "0": { - "human": " metri" - } - } - } - } - } - }, "title": "OpenWindPowerMap" }, "parkings": { @@ -1172,52 +551,6 @@ }, "postboxes": { "description": "In questa cartina puoi veder e modificare gli uffici postali e le buche delle lettere. Puoi usare questa cartina per trovare dove imbucare la tua prossima cartolina! :)
    Hai trovato un errore o una buca delle lettere mancante? Puoi modificare questa cartina con un account gratuito su OpenStreetMap. ", - "layers": { - "0": { - "description": "Il livello che mostra le buche delle lettere.", - "name": "Buche delle lettere", - "presets": { - "0": { - "title": "Buca delle lettere" - } - }, - "title": { - "render": "Buca delle lettere" - } - }, - "1": { - "description": "Un livello che mostra gli uffici postali.", - "filter": { - "0": { - "options": { - "0": { - "question": "Aperto adesso" - } - } - } - }, - "name": "Uffici postali", - "presets": { - "0": { - "title": "Ufficio postale" - } - }, - "tagRenderings": { - "OH": { - "mappings": { - "0": { - "then": "aperto 24/24h (feste incluse)" - } - }, - "question": "Quali sono gli orari di apertura di questo ufficio postale?", - "render": "Orari di apertura: {opening_hours_table()}" - } - }, - "title": { - "render": "Ufficio postale" - } - } - }, "shortDescription": "Una cartina che mostra le buche delle lettere e gli uffici postali", "title": "Buche delle lettere e uffici postali" }, @@ -1247,39 +580,7 @@ }, "uk_addresses": { "description": "Contribuisci a OpenStreetMap inserendo le informazioni sull’indirizzo", - "layers": { - "1": { - "description": "Indirizzi", - "name": "Indirizzo presente su OSM", - "tagRenderings": { - "uk_addresses_explanation_osm": { - "render": "Questo indirizzo è salvato su OpenStreetMap" - }, - "uk_addresses_housenumber": { - "mappings": { - "0": { - "then": "Questo edificio non ha indirizzo" - } - }, - "question": "Qual è il numero civico di questa casa?", - "render": "Il numero civico è {addr:housenumber}" - }, - "uk_addresses_street": { - "question": "Qual è la via in cui si trova?", - "render": "L’indirizzo è in via {addr:street}" - } - }, - "title": { - "render": "Indirizzo conosciuto" - } - } - }, "shortDescription": "Aiuta a costruire un dataset libero per gli indirizzi nel Regno Unito", - "tileLayerSources": { - "0": { - "name": "Confini delle proprietà di osmuk.org" - } - }, "title": "Indirizzi UK" }, "waste_basket": { 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/nb_NO.json b/langs/themes/nb_NO.json index a423c9f02..06d427c96 100644 --- a/langs/themes/nb_NO.json +++ b/langs/themes/nb_NO.json @@ -1,7 +1,7 @@ { "aed": { "description": "Defibrillatorer i nærheten", - "title": "Åpent AED-kart" + "title": "Åpne AED-kart" }, "artwork": { "description": "Velkommen til det åpne kunstverkskartet, et kart over statuer, byster, grafitti, og andre kunstverk i verden" @@ -36,39 +36,11 @@ }, "caravansites-fee": { "mappings": { - "0": { - "then": "Man må betale for bruk" - }, "1": { "then": "Kan brukes gratis" } } }, - "caravansites-internet": { - "mappings": { - "0": { - "then": "Det finnes tilgang til Internett" - }, - "1": { - "then": "Det er tilgang til Internett" - }, - "2": { - "then": "Det er tilgang til Internett" - } - }, - "question": "Tilbyr dette stedet tilgang til Internett?" - }, - "caravansites-internet-fee": { - "mappings": { - "0": { - "then": "Man må betale ekstra for tilgang til Internett" - }, - "1": { - "then": "Man må ikke betale ekstra for tilgang til Internett" - } - }, - "question": "Må man betale for tilgang til Internett?" - }, "caravansites-toilets": { "mappings": { "0": { @@ -111,17 +83,7 @@ "name": "Klatreruter", "tagRenderings": { "Length": { - "question": "Hvor mange meter er klatreruten?", "render": "Denne ruten er {canonical(climbing:length)} lang" - }, - "Name": { - "mappings": { - "0": { - "then": "Denne klatreruten har ikke noe navn" - } - }, - "question": "Hva er navnet på denne klatreruten?", - "render": "{name}" } }, "title": { @@ -187,27 +149,15 @@ }, "cyclestreets": { "layers": { - "0": { - "name": "Sykkelgater" - }, "1": { - "description": "Denne gaten vil bli sykkelgate snart", "name": "Fremtidig sykkelvei", "title": { - "mappings": { - "0": { - "then": "{name} vil bli sykkelgate snart" - } - }, "render": "Fremtidig sykkelvei" } }, "2": { "description": "Lag for å markere hvilken som helst gate som sykkelvei", - "name": "Alle gater", - "title": { - "render": "Gate" - } + "name": "Alle gater" } }, "overrideAll": { @@ -296,23 +246,6 @@ }, "2": { "name": "Kart over brannstasjoner", - "presets": { - "0": { - "title": "Brannstasjon" - } - }, - "tagRenderings": { - "station-name": { - "render": "Denne stasjonen heter {name}." - }, - "station-operator": { - "mappings": { - "0": { - "then": "Stasjonen drives av myndighetene." - } - } - } - }, "title": { "render": "Brannstasjon" } @@ -326,21 +259,6 @@ "natuurpunt": { "title": "Naturreservater" }, - "openwindpowermap": { - "layers": { - "0": { - "units": { - "0": { - "applicableUnits": { - "1": { - "human": " kilowatt" - } - } - } - } - } - } - }, "parkings": { "shortDescription": "Dette kartet viser forskjellige parkeringsplasser", "title": "Parkering" @@ -353,32 +271,6 @@ "title": "Lekeplasser" }, "postboxes": { - "layers": { - "0": { - "description": "Laget viser postbokser.", - "name": "Postbokser", - "presets": { - "0": { - "title": "postboks" - } - }, - "title": { - "render": "Postboks" - } - }, - "1": { - "description": "Et lag som viser postkontor.", - "name": "Postkontor", - "presets": { - "0": { - "title": "Postkontor" - } - }, - "title": { - "render": "Postkontor" - } - } - }, "title": "Postboks og postkontor-kart" }, "shops": { @@ -391,16 +283,5 @@ "description": "Kartlegg trærne.", "shortDescription": "Kartlegg alle trærne", "title": "Trær" - }, - "uk_addresses": { - "layers": { - "1": { - "description": "Adresser", - "name": "Kjente adresser i OSM", - "title": { - "render": "Kjent adresse" - } - } - } } } \ No newline at end of file diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 34fef4ae8..e3eb92270 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -753,7 +753,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": { @@ -925,6 +968,66 @@ "shortDescription": "Deze kaart toont sportvelden", "title": "Sportvelden" }, + "street_lighting": { + "description": "Op deze kaart vind je alles over straatlantaarns", + "layers": { + "1": { + "name": "Verlichte straten", + "tagRenderings": { + "lit": { + "mappings": { + "0": { + "then": "Deze straat is verlicht" + }, + "1": { + "then": "Deze straat is niet verlicht" + }, + "2": { + "then": "Deze straat is 's nachts verlicht" + }, + "3": { + "then": "Deze straat is 24/7 verlicht" + } + }, + "question": "Is deze straat verlicht?" + } + }, + "title": { + "render": "Verlichte straat" + } + }, + "2": { + "name": "Alle straten", + "tagRenderings": { + "lit": { + "mappings": { + "0": { + "then": "Deze straat is verlicht" + }, + "1": { + "then": "Deze straat is niet verlicht" + }, + "2": { + "then": "Deze straat is 's nachts verlicht" + }, + "3": { + "then": "Deze straat is 24/7 verlicht" + } + }, + "question": "Is deze straat verlicht?" + } + }, + "title": { + "render": "Straat" + } + } + }, + "title": "Straatverlichting" + }, + "street_lighting_assen": { + "description": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen", + "title": "Straatverlichting - Assen" + }, "surveillance": { "description": "Op deze open kaart kan je bewakingscamera's vinden.", "shortDescription": "Bewakingscameras en dergelijke", @@ -948,7 +1051,7 @@ "uk_addresses": { "description": "Draag bij aan OpenStreetMap door adresinformatie in te vullen", "layers": { - "1": { + "2": { "description": "Adressen", "tagRenderings": { "uk_addresses_housenumber": { 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 index 74673035b..342dc22f9 100644 --- a/scripts/slice.ts +++ b/scripts/slice.ts @@ -106,7 +106,7 @@ async function main(args: string[]) { console.log("Loaded all", allFeatures.length, "points") - const keysToRemove = ["ID", "STRAATNMID", "NISCODE", "GEMEENTE", "POSTCODE", "HERKOMST"] + const keysToRemove = ["STRAATNMID", "GEMEENTE", "POSTCODE"] for (const f of allFeatures) { for (const keyToRm of keysToRemove) { delete f.properties[keyToRm] 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) => {