diff --git a/Docs/BuiltinLayers.md b/Docs/BuiltinLayers.md index b88b012e57..7e61e352d0 100644 --- a/Docs/BuiltinLayers.md +++ b/Docs/BuiltinLayers.md @@ -16,6 +16,7 @@ + [left_right_style](#left_right_style) + [split_point](#split_point) + [current_view](#current_view) + + [matchpoint](#matchpoint) 1. [Normal layers](#normal-layers) - [Frequently reused layers](#frequently-reused-layers) + [bicycle_library](#bicycle_library) @@ -159,6 +160,7 @@ - [left_right_style](#left_right_style) - [split_point](#split_point) - [current_view](#current_view) + - [matchpoint](#matchpoint) ### gps_location @@ -287,6 +289,19 @@ The icon on the button is the default icon of the layer, but can be customized b + - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. + + +### matchpoint + + + +The default rendering for a locationInput which snaps onto another object + +[Go to the source code](../assets/layers/matchpoint/matchpoint.json) + + + - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index 6caf884340..02bf6103c9 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -18,6 +18,7 @@ + [_now:date, _now:datetime, _loaded:date, _loaded:_datetime](#_nowdate,-_now:datetime,-_loaded:date,-_loaded:_datetime) + [_last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number, _backend](#_last_editcontributor,-_last_edit:contributor:uid,-_last_edit:changeset,-_last_edit:timestamp,-_version_number,-_backend) + [sidewalk:left, sidewalk:right, generic_key:left:property, generic_key:right:property](#sidewalkleft,-sidewalk:right,-generic_key:left:property,-generic_key:right:property) + + [_geometry:type](#_geometrytype) + [distanceTo](#distanceto) + [overlapWith](#overlapwith) + [intersectionsWith](#intersectionswith) @@ -149,6 +150,16 @@ Information about the last edit of this object. 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 + + + + +### _geometry:type + + + +Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString` + Calculating tags with Javascript diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index 65163c390b..ba5b54f54b 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -42,6 +42,10 @@ * [Example usage of tag_apply](#example-usage-of-tag_apply) + [export_as_gpx](#export_as_gpx) * [Example usage of export_as_gpx](#example-usage-of-export_as_gpx) + + [export_as_geojson](#export_as_geojson) + * [Example usage of export_as_geojson](#example-usage-of-export_as_geojson) + + [open_in_iD](#open_in_id) + * [Example usage of open_in_iD](#example-usage-of-open_in_id) + [clear_location_history](#clear_location_history) * [Example usage of clear_location_history](#example-usage-of-clear_location_history) + [auto_apply](#auto_apply) @@ -450,6 +454,22 @@ id_of_object_to_apply_this_one | _undefined_ | If specified, applies the the tag `{export_as_gpx()}` +### export_as_geojson + + Exports the selected feature as GeoJson-file + +#### Example usage of export_as_geojson + + `{export_as_geojson()}` + +### open_in_iD + + Opens the current view in the iD-editor + +#### Example usage of open_in_iD + + `{open_in_iD()}` + ### clear_location_history A button to remove the travelled track information from the device diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index a99a70d4fa..db422827f8 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -7,7 +7,6 @@ import Title from "../UI/Base/Title"; import {FixedUiElement} from "../UI/Base/FixedUiElement"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import {CountryCoder} from "latlon2country" -import ScriptUtils from "../scripts/ScriptUtils"; export class SimpleMetaTagger { @@ -409,7 +408,21 @@ export default class SimpleMetaTaggers { feature.properties["_loaded:datetime"] = datetime(freshness); return true; } + ); + + public static geometryType = new SimpleMetaTagger( + { + keys:["_geometry:type"], + doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`", + }, + (feature, _) => { + const changed = feature.properties["_geometry:type"] === feature.geometry.type; + feature.properties["_geometry:type"] = feature.geometry.type; + return changed + } ) + + public static metatags: SimpleMetaTagger[] = [ SimpleMetaTaggers.latlon, SimpleMetaTaggers.layerInfo, @@ -421,7 +434,8 @@ export default class SimpleMetaTaggers { SimpleMetaTaggers.directionSimplified, SimpleMetaTaggers.currentTime, SimpleMetaTaggers.objectMetaInfo, - SimpleMetaTaggers.noBothButLeftRight + SimpleMetaTaggers.noBothButLeftRight, + SimpleMetaTaggers.geometryType ]; diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index bce97c06cb..091422bb66 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -73,7 +73,13 @@ export default class LayerConfig extends WithContextLoader { 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 + ")" + } + if(json.id.toLowerCase() !== json.id){ + throw `${context}: The id of a layer should be lowercase: ${json.id}` + } + if(json.id.match(/[a-z0-9-_]/) == null){ + throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}` } this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 7fb99c32d8..e18cd7d8fe 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -58,6 +58,12 @@ export default class LayoutConfig { constructor(json: LayoutConfigJson, official = true, context?: string) { this.official = official; this.id = json.id; + if(json.id.toLowerCase() !== json.id){ + throw "The id of a theme should be lowercase: "+json.id + } + if(json.id.match(/[a-z0-9-_]/) == null){ + throw "The id of a theme should match [a-z0-9-_]*: "+json.id + } context = (context ?? "") + "." + this.id; this.maintainer = json.maintainer; this.credits = json.credits; diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 24f8435676..12e043b390 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -350,8 +350,19 @@ export default class ValidatedTextField { ValidatedTextField.tp( "email", "An email adress", - (str) => EmailValidator.validate(str), - undefined, + (str) => { + if(str.startsWith("mailto:")){ + str = str.substring("mailto:".length) + } + return EmailValidator.validate(str); + }, + str => { + if(str === undefined){return undefined} + if(str.startsWith("mailto:")){ + str = str.substring("mailto:".length) + } + return str; + }, undefined, "email"), ValidatedTextField.tp( diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json index 73542931c1..5db0aa0592 100644 --- a/assets/layers/etymology/etymology.json +++ b/assets/layers/etymology/etymology.json @@ -184,9 +184,16 @@ } ] }, + "fill": "no", "width": { - "render": "8" + "render": "8", + "mappings": [ + { + "if": "_geometry:type=Polygon", + "then": "16" + } + ] } } ] -} \ No newline at end of file +} diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index b0a91d1c7b..ae6b710817 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -543,7 +543,7 @@ "condition": "cuisine=friture" }, "service:electricity", - "dog-access" + "dog-access","reviews" ], "filter": [ { @@ -679,4 +679,4 @@ "description": { "en": "A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" } -} \ No newline at end of file +} diff --git a/assets/themes/aed/aed_brugge.json b/assets/themes/aed/aed_brugge.json index 92d829d22f..01b530198f 100644 --- a/assets/themes/aed/aed_brugge.json +++ b/assets/themes/aed/aed_brugge.json @@ -18,7 +18,7 @@ "layers": [ "defibrillator", { - "id": "Brugge", + "id": "brugge", "name": "Brugse dataset", "source": { "osmTags": "Brugs volgnummer~*", diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index 6b2948807b..f3cc1d2fd9 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -93,7 +93,7 @@ } }, { - "id": "OSM-buildings", + "id": "osm-buildings", "name": "All OSM-buildings", "source": { "osmTags": "building~*", @@ -372,13 +372,13 @@ "builtin": "crab_address", "override": { "calculatedTags+": [ - "_embedded_in=feat.overlapWith('OSM-buildings').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}", + "_embedded_in=feat.overlapWith('osm-buildings').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}", "_embedding_nr=feat.get('_embedded_in')['addr:housenumber']+(feat.get('_embedded_in')['addr:unit'] ?? '')", "_embedding_street=feat.get('_embedded_in')['addr:street']", "_embedding_id=feat.get('_embedded_in').id", "_closeby_addresses=feat.closestn('address',10,undefined,50).map(f => f.feat).filter(addr => addr.properties['addr:street'] == feat.properties['STRAATNM'] && feat.properties['HNRLABEL'] == addr.properties['addr:housenumber'] + (addr.properties['addr:unit']??'') ).length", "_has_identical_closeby_address=feat.get('_closeby_addresses') >= 1 ? 'yes' : 'no'", - "_embedded_in_grb=feat.overlapWith('GRB')[0]?.feat?.properties ?? {}", + "_embedded_in_grb=feat.overlapWith('grb')[0]?.feat?.properties ?? {}", "_embedding_nr_grb=feat.get('_embedded_in_grb')['addr:housenumber']", "_embedding_street_grb=feat.get('_embedded_in_grb')['addr:street']" ], @@ -452,7 +452,7 @@ }, { "id": "import-button", - "render": "{import_button(address, addr:street=$STRAATNM; addr:housenumber=$_HNRLABEL,Voeg dit adres als een nieuw adrespunt toe,,OSM-buildings,5)}", + "render": "{import_button(address, addr:street=$STRAATNM; addr:housenumber=$_HNRLABEL,Voeg dit adres als een nieuw adrespunt toe,,osm-buildings,5)}", "condition": { "and": [ "_embedding_id!=", @@ -469,7 +469,7 @@ } }, { - "id": "GRB", + "id": "grb", "description": "Geometry which comes from GRB with tools to import them", "source": { "osmTags": { @@ -486,7 +486,7 @@ "name": "GRB geometries", "title": "GRB outline", "calculatedTags": [ - "_overlaps_with_buildings=feat.overlapWith('OSM-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)", + "_overlaps_with_buildings=feat.overlapWith('osm-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)", "_overlaps_with=feat.get('_overlaps_with_buildings').filter(f => f.overlap > 1 /* square meter */ )[0] ?? ''", "_overlap_absolute=feat.get('_overlaps_with')?.overlap", "_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_surface')) ", @@ -507,7 +507,7 @@ "tagRenderings": [ { "id": "Import-button", - "render": "{import_way_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}", + "render": "{import_way_button(osm-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}", "mappings": [ { "#": "Hide import button if intersection with other objects are detected", @@ -524,11 +524,11 @@ "addr:housenumber~*" ] }, - "then": "{conflate_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}" + "then": "{conflate_button(osm-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}" }, { "if": "_overlaps_with!=", - "then": "{conflate_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)}" + "then": "{conflate_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)}" } ] }, diff --git a/assets/themes/grb_import/grb_fixme.json b/assets/themes/grb_import/grb_fixme.json index 9a00f3ad1f..e1687783f9 100644 --- a/assets/themes/grb_import/grb_fixme.json +++ b/assets/themes/grb_import/grb_fixme.json @@ -31,7 +31,7 @@ }, "layers": [ { - "id": "OSM-buildings-fixme", + "id": "osm-buildings-fixme", "name": "OSM-buildings with a fixme", "source": { "osmTags": { diff --git a/assets/themes/grb_import/missing_streets.json b/assets/themes/grb_import/missing_streets.json index 7630d59036..dc82fc85b2 100644 --- a/assets/themes/grb_import/missing_streets.json +++ b/assets/themes/grb_import/missing_streets.json @@ -42,7 +42,7 @@ } ], "calculatedTags": [ - "_overlapping=Number(feat.properties.zoom) >= 14 ? feat.overlapWith('OSM-buildings').map(ff => ff.feat.properties) : undefined", + "_overlapping=Number(feat.properties.zoom) >= 14 ? feat.overlapWith('osm-buildings').map(ff => ff.feat.properties) : undefined", "_applicable=feat.get('_overlapping').filter(p => (p._spelling_is_correct === 'true') && (p._singular_import === 'true')).map(p => p.id)", "_applicable_count=feat.get('_applicable')?.length" ], @@ -67,7 +67,7 @@ }, { "id": "autoapply", - "render": "{auto_apply(OSM-buildings, _applicable, apply_streetname, Automatically add all missing streetnames on buildings in view)}" + "render": "{auto_apply(osm-buildings, _applicable, apply_streetname, Automatically add all missing streetnames on buildings in view)}" } ] } @@ -89,7 +89,7 @@ } }, { - "id": "OSM-buildings", + "id": "osm-buildings", "name": "Alle OSM-gebouwen met een huisnummer en zonder straat", "source": { "osmTags": { diff --git a/assets/themes/street_lighting/street_lighting_assen.json b/assets/themes/street_lighting/street_lighting_assen.json index 7f98cdcd70..fe515dba0c 100644 --- a/assets/themes/street_lighting/street_lighting_assen.json +++ b/assets/themes/street_lighting/street_lighting_assen.json @@ -19,7 +19,7 @@ "layers": [ "street_lamps", { - "id": "Assen", + "id": "assen", "name": "Dataset Assen", "source": { "osmTags": "Lichtmastnummer~*",