From 25f2aa8e92c118ae58ee8f812c5b6bd5c4d10690 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 30 Oct 2020 00:56:46 +0100 Subject: [PATCH] Fix all the bugs, feature-complete with the non-refactored version --- Customizations/AllKnownLayouts.ts | 12 +- Customizations/JSON/LayerConfig.ts | 16 +- Customizations/JSON/LayerConfigJson.ts | 7 + Logic/FilteredLayer.ts | 6 +- Logic/MetaTagging.ts | 277 +++++++++++++++------- UI/CustomGenerator/TagRenderingPanel.ts | 4 +- UI/CustomGenerator/TagRenderingPreview.ts | 6 +- UI/SimpleAddUI.ts | 8 +- Utils.ts | 11 + assets/themes/widths/width.json | 204 ++++++++++++++++ test/Tag.spec.ts | 15 ++ 11 files changed, 467 insertions(+), 99 deletions(-) create mode 100644 assets/themes/widths/width.json diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index fea2667..c73899b 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -1,5 +1,4 @@ import {Layout} from "./Layout"; -import {FromJSON} from "./JSON/FromJSON"; import * as bookcases from "../assets/themes/bookcases/Bookcases.json"; import * as aed from "../assets/themes/aed/aed.json"; import * as toilets from "../assets/themes/toilets/toilets.json"; @@ -15,6 +14,7 @@ import * as bike_monitoring_stations from "../assets/themes/bike_monitoring_stat import * as fritures from "../assets/themes/fritures/fritures.json" import * as benches from "../assets/themes/benches/benches.json"; import * as charging_stations from "../assets/themes/charging_stations/charging_stations.json" +import * as widths from "../assets/themes/widths/width.json" import {PersonalLayout} from "../Logic/PersonalLayout"; import LayerConfig from "./JSON/LayerConfig"; @@ -41,11 +41,20 @@ export class AllKnownLayouts { } + private static GenerateWidths(): Layout { + const layout = Layout.LayoutFromJSON(widths, SharedLayers.sharedLayers); + + layout.enableUserBadge = false; + + return layout; + } + private static GenerateBuurtNatuur(): Layout { const layout = Layout.LayoutFromJSON(buurtnatuur, SharedLayers.sharedLayers); layout.enableMoreQuests = false; layout.enableShareScreen = false; layout.hideFromOverview = true; + console.log("Buurtnatuur:",layout) return layout; } @@ -73,6 +82,7 @@ export class AllKnownLayouts { Layout.LayoutFromJSON(fritures, SharedLayers.sharedLayers), Layout.LayoutFromJSON(benches, SharedLayers.sharedLayers), Layout.LayoutFromJSON(charging_stations, SharedLayers.sharedLayers), + AllKnownLayouts.GenerateWidths(), AllKnownLayouts.GenerateBuurtNatuur(), AllKnownLayouts.GenerateBikeMonitoringStations(), diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index d04d7a4..2297d50 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -1,8 +1,7 @@ -import Translation from "../../UI/i18n/Translation"; +import Translations, {Translation} from "../../UI/i18n/Translations"; import TagRenderingConfig from "./TagRenderingConfig"; import {Tag, TagsFilter} from "../../Logic/Tags"; import {LayerConfigJson} from "./LayerConfigJson"; -import Translations from "../../UI/i18n/Translations"; import {FromJSON} from "./FromJSON"; import SharedTagRenderings from "../SharedTagRenderings"; import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; @@ -25,6 +24,7 @@ export default class LayerConfig { iconSize: TagRenderingConfig; color: TagRenderingConfig; width: TagRenderingConfig; + dashArray: TagRenderingConfig; wayHandling: number; @@ -54,11 +54,12 @@ export default class LayerConfig { this.wayHandling = json.wayHandling ?? 0; this.hideUnderlayingFeaturesMinPercentage = json.hideUnderlayingFeaturesMinPercentage ?? 0; this.title = new TagRenderingConfig(json.title); - this.presets = (json.presets ?? []).map(pr => ({ - title: Translations.T(pr.title), - tags: pr.tags.map(t => FromJSON.SimpleTag(t)), - description: Translations.T(pr.description) - })) + this.presets = (json.presets ?? []).map(pr => + ({ + title: Translations.T(pr.title), + tags: pr.tags.map(t => FromJSON.SimpleTag(t)), + description: Translations.T(pr.description) + })) /** @@ -108,6 +109,7 @@ export default class LayerConfig { this.iconSize = tr("iconSize", "40,40,center"); this.color = tr("color", "#0000ff"); this.width = tr("width", "7"); + this.dashArray = tr("dashArray", ""); } diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index cc93853..be70638 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -64,6 +64,13 @@ export interface LayerConfigJson { */ 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 diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index e6ca548..7994caa 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -56,6 +56,9 @@ export class FilteredLayer { const iconUrl = layerDef.icon?.GetRenderValue(tags)?.txt ?? "./assets/bug.svg"; const iconSize = (layerDef.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(","); + + + const dashArray = layerDef.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number); function num(str, deflt = 40) { const n = Number(str); @@ -97,7 +100,8 @@ export class FilteredLayer { popupAnchor: [0, 3 - anchorH] }, color: color, - weight: weight + weight: weight, + dashArray: dashArray }; }; this.name = name; diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 5be7edb..23a5c43 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -2,6 +2,8 @@ import {GeoOperations} from "./GeoOperations"; import CodeGrid from "./Web/CodeGrid"; import State from "../State"; import opening_hours from "opening_hours"; +import {And, Or, Tag} from "./Tags"; +import {Utils} from "../Utils"; class SimpleMetaTagger { @@ -40,90 +42,201 @@ class SimpleMetaTagger { export default class MetaTagging { + private static latlon = new SimpleMetaTagger(["_lat", "_lon"], "The latitude and longitude of the point (or centerpoint in the case of a way/area)", + (feature => { + const centerPoint = GeoOperations.centerpoint(feature); + const lat = centerPoint.geometry.coordinates[1]; + const lon = centerPoint.geometry.coordinates[0]; + feature.properties["_lat"] = "" + lat; + feature.properties["_lon"] = "" + lon; + }) + ); + private static surfaceArea = new SimpleMetaTagger( + ["_surface", "_surface:ha"], "The surface area of the feature, in square meters and in hectare. Not set on points and ways", + (feature => { + const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); + feature.properties["_surface"] = "" + sqMeters; + feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; + + }) + ); + private static country = new SimpleMetaTagger( + ["_country"], "The country code of the point", + ((feature, index) => { + const centerPoint = GeoOperations.centerpoint(feature); + const lat = centerPoint.geometry.coordinates[1]; + const lon = centerPoint.geometry.coordinates[0] + // But the codegrid SHOULD be a number! + CodeGrid.getCode(lat, lon, (error, code) => { + if (error === null) { + feature.properties["_country"] = code; + + // There is a huge performance issue: if there are ~1000 features receiving a ping at the same time, + // The application hangs big time + // So we disable pinging all together + + } else { + console.warn("Could not determine country for", feature.properties.id, error); + } + }); + }) + ) + private static isOpen = new SimpleMetaTagger( + ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", + (feature => { + const tagsSource = State.state.allElements.addOrGetElement(feature); + tagsSource.addCallback(tags => { + + if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) { + + if (tags._isOpen !== undefined) { + // Already defined + return; + } + + const oh = new opening_hours(tags["opening_hours"], { + lat: tags._lat, + lon: tags._lon, + address: { + country_code: tags._country + } + }, {tag_key: "opening_hours"}); + + const updateTags = () => { + tags["_isOpen"] = oh.getState() ? "yes" : "no"; + const comment = oh.getComment(); + if (comment) { + tags["_isOpen:description"] = comment; + } + const nextChange = oh.getNextChange() as Date; + if (nextChange !== undefined) { + window.setTimeout( + updateTags, + (nextChange.getTime() - (new Date()).getTime()) + ) + } + } + updateTags(); + } + + }) + }) + ) + + public static carriageWayWidth = new SimpleMetaTagger( + ["_width:needed","_width:needed:no_pedestrians", "_width:difference"], + "Legacy for a specific project calculating the needed width for safe traffic on a road", + (feature: any, index: number) => { + + const carWidth = 2; + const cyclistWidth = 1.5; + const pedestrianWidth = 0.75; + + const properties = feature.properties; + + const _leftSideParking = + new And([new Tag("parking:lane:left", "parallel"), new Tag("parking:lane:right", "no_parking")]); + const _rightSideParking = + new And([new Tag("parking:lane:right", "parallel"), new Tag("parking:lane:left", "no_parking")]); + + const _bothSideParking = new Tag("parking:lane:both", "parallel"); + const _noSideParking = new Tag("parking:lane:both", "no_parking"); + const _otherParkingMode = + new Or([ + new Tag("parking:lane:both", "perpendicular"), + new Tag("parking:lane:left", "perpendicular"), + new Tag("parking:lane:right", "perpendicular"), + new Tag("parking:lane:both", "diagonal"), + new Tag("parking:lane:left", "diagonal"), + new Tag("parking:lane:right", "diagonal"), + ]) + + const _sidewalkBoth = new Tag("sidewalk", "both"); + const _sidewalkLeft = new Tag("sidewalk", "left"); + const _sidewalkRight = new Tag("sidewalk", "right"); + const _sidewalkNone = new Tag("sidewalk", "none"); + + + let parkingStateKnown = true; + let parallelParkingCount = 0; + + + const _oneSideParking = new Or([_leftSideParking, _rightSideParking]); + + if (_oneSideParking.matchesProperties(properties)) { + parallelParkingCount = 1; + } else if (_bothSideParking.matchesProperties(properties)) { + parallelParkingCount = 2; + } else if (_noSideParking.matchesProperties(properties)) { + parallelParkingCount = 0; + } else if (_otherParkingMode.matchesProperties(properties)) { + parallelParkingCount = 0; + } else { + parkingStateKnown = false; + console.log("No parking data for ", properties.name, properties.id, properties) + } + + + let pedestrianFlowNeeded; + if (_sidewalkBoth.matchesProperties(properties)) { + pedestrianFlowNeeded = 0; + } else if (_sidewalkNone.matchesProperties(properties)) { + pedestrianFlowNeeded = 2; + } else if (_sidewalkLeft.matchesProperties(properties) || _sidewalkRight.matchesProperties(properties)) { + pedestrianFlowNeeded = 1; + } else { + pedestrianFlowNeeded = -1; + } + + + let onewayCar = properties.oneway === "yes"; + let onewayBike = properties["oneway:bicycle"] === "yes" || + (onewayCar && properties["oneway:bicycle"] === undefined) + + let cyclingAllowed = + !(properties.bicycle === "use_sidepath" + || properties.bicycle === "no"); + + let carWidthUsed = (onewayCar ? 1 : 2) * carWidth; + properties["_width:needed:cars"] = Utils.Round(carWidthUsed); + properties["_width:needed:parking"] = Utils.Round(parallelParkingCount * carWidth) + + + let cyclistWidthUsed = 0; + if (cyclingAllowed) { + cyclistWidthUsed = (onewayBike ? 1 : 2) * cyclistWidth; + } + properties["_width:needed:cyclists"] = Utils.Round(cyclistWidthUsed) + + + const width = parseFloat(properties["width:carriageway"]); + + + const targetWidthIgnoringPedestrians = + carWidthUsed + + cyclistWidthUsed + + parallelParkingCount * carWidthUsed; + properties["_width:needed:no_pedestrians"] =Utils.Round(targetWidthIgnoringPedestrians); + + const pedestriansNeed = Math.max(0, pedestrianFlowNeeded) * pedestrianWidth; + const targetWidth = targetWidthIgnoringPedestrians + pedestriansNeed ; + properties["_width:needed"] = Utils.Round(targetWidth); + properties["_width:needed:pedestrians"] = Utils.Round(pedestriansNeed) + + + properties["_width:difference"] = Utils.Round(targetWidth - width ); + properties["_width:difference:no_pedestrians"] = Utils.Round(targetWidthIgnoringPedestrians - width) ; + + } + ); + public static metatags = [ - new SimpleMetaTagger(["_lat", "_lon"], "The latitude and longitude of the point (or centerpoint in the case of a way/area)", - (feature => { - const centerPoint = GeoOperations.centerpoint(feature); - const lat = centerPoint.geometry.coordinates[1]; - const lon = centerPoint.geometry.coordinates[0]; - feature.properties["_lat"] = "" + lat; - feature.properties["_lon"] = "" + lon; - }) - ), - new SimpleMetaTagger( - ["_surface", "_surface:ha"], "The surface area of the feature, in square meters and in hectare. Not set on points and ways", - (feature => { - const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); - feature.properties["_surface"] = "" + sqMeters; - feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; + MetaTagging.latlon, + MetaTagging.surfaceArea, + MetaTagging.country, + MetaTagging.isOpen, + MetaTagging.carriageWayWidth - }) - ), - - - new SimpleMetaTagger( - ["_country"], "The country code of the point", - ((feature, index) => { - const centerPoint = GeoOperations.centerpoint(feature); - const lat = centerPoint.geometry.coordinates[1]; - const lon = centerPoint.geometry.coordinates[0] - // But the codegrid SHOULD be a number! - CodeGrid.getCode(lat, lon, (error, code) => { - if (error === null) { - feature.properties["_country"] = code; - - // There is a huge performance issue: if there are ~1000 features receiving a ping at the same time, - // The application hangs big time - // So we disable pinging all together - - } else { - console.warn("Could not determine country for", feature.properties.id, error); - } - }); - }) - ), - new SimpleMetaTagger( - ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", - (feature => { - const tagsSource = State.state.allElements.addOrGetElement(feature); - tagsSource.addCallback(tags => { - - if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) { - - if (tags._isOpen !== undefined) { - // Already defined - return; - } - - const oh = new opening_hours(tags["opening_hours"], { - lat: tags._lat, - lon: tags._lon, - address: { - country_code: tags._country - } - }, {tag_key: "opening_hours"}); - - const updateTags = () => { - tags["_isOpen"] = oh.getState() ? "yes" : "no"; - const comment = oh.getComment(); - if (comment) { - tags["_isOpen:description"] = comment; - } - const nextChange = oh.getNextChange() as Date; - if (nextChange !== undefined) { - window.setTimeout( - updateTags, - (nextChange.getTime() - (new Date()).getTime()) - ) - } - } - updateTags(); - } - - }) - - }) - ) ]; static addMetatags(features: any[]) { diff --git a/UI/CustomGenerator/TagRenderingPanel.ts b/UI/CustomGenerator/TagRenderingPanel.ts index 5b7867c..096f2d5 100644 --- a/UI/CustomGenerator/TagRenderingPanel.ts +++ b/UI/CustomGenerator/TagRenderingPanel.ts @@ -15,9 +15,9 @@ import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConf import {UserDetails} from "../../Logic/Osm/OsmConnection"; import State from "../../State"; import {VariableUiElement} from "../Base/VariableUIElement"; -import {FromJSON} from "../../Customizations/JSON/FromJSON"; import ValidatedTextField from "../Input/ValidatedTextField"; import SpecialVisualizations from "../SpecialVisualizations"; +import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; export default class TagRenderingPanel extends InputElement { @@ -111,7 +111,7 @@ export default class TagRenderingPanel extends InputElement { try{ - FromJSON.TagRendering(json, options?.title ?? ""); + new TagRenderingConfig(json, options?.title ?? ""); return ""; }catch(e){ return ""+e+"" diff --git a/UI/CustomGenerator/TagRenderingPreview.ts b/UI/CustomGenerator/TagRenderingPreview.ts index 8ec0357..a884b34 100644 --- a/UI/CustomGenerator/TagRenderingPreview.ts +++ b/UI/CustomGenerator/TagRenderingPreview.ts @@ -2,9 +2,10 @@ import {UIElement} from "../UIElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import TagRenderingPanel from "./TagRenderingPanel"; import {VariableUiElement} from "../Base/VariableUIElement"; -import {FromJSON} from "../../Customizations/JSON/FromJSON"; import {FixedUiElement} from "../Base/FixedUiElement"; import Combine from "../Base/Combine"; +import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; +import EditableTagRendering from "../Popup/EditableTagRendering"; export default class TagRenderingPreview extends UIElement { @@ -39,8 +40,7 @@ export default class TagRenderingPreview extends UIElement { rendering = new VariableUiElement(es.map(tagRenderingConfig => { try { - const tr = FromJSON.TagRendering(tagRenderingConfig, "preview") - .construct(self.previewTagValue); + const tr = new EditableTagRendering(self.previewTagValue, new TagRenderingConfig(tagRenderingConfig, "preview")); return tr.Render(); } catch (e) { return new Combine(["Could not show this tagrendering:", e.message]).Render(); diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index 21b03ec..b9cb42f 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -46,10 +46,12 @@ export class SimpleAddUI extends UIElement { const self = this; for (const layer of State.state.filteredLayers.data) { - + this.ListenTo(layer.isDisplayed); - - for (const preset of layer.layerDef.presets) { + + const presets = layer.layerDef.presets; + for (const preset of presets) { + console.log("Preset:", preset) let icon: string = layer.layerDef.icon.GetRenderValue( TagUtils.KVtoProperties(preset.tags ?? [])).txt ?? diff --git a/Utils.ts b/Utils.ts index 3a85418..99ff991 100644 --- a/Utils.ts +++ b/Utils.ts @@ -39,6 +39,17 @@ export class Utils { return "" + i; } + public static Round(i: number) { + if(i < 0){ + return "-" + Utils.Round(-i); + } + const j = "" + Math.floor(i * 10); + if (j.length == 1) { + return "0." + j; + } + return j.substr(0, j.length - 1) + "." + j.substr(j.length - 1, j.length); + } + public static Times(f: ((i: number) => string), count: number): string { let res = ""; for (let i = 0; i < count; i++) { diff --git a/assets/themes/widths/width.json b/assets/themes/widths/width.json new file mode 100644 index 0000000..bb3fe06 --- /dev/null +++ b/assets/themes/widths/width.json @@ -0,0 +1,204 @@ +{ + "id": "width", + "title": { + "nl": "Straatbreedtes" + }, + "shortDescription": { + "nl": "Is de straat breed genoeg?" + }, + "description": { + "nl": "

De straat is opgebruikt

\n

Er is steeds meer druk op de openbare ruimte. Voetgangers, fietsers, steps, auto's, bussen, bestelwagens, buggies, cargobikes, ... willen allemaal hun deel van de openbare ruimte.

\n

In deze studie nemen we Brugge onder de loep en kijken we hoe breed elke straat is én hoe breed elke straat zou moeten zijn voor een veilig én vlot verkeer.

\n

Legende

\n     Straat te smal voor veilig verkeer
\n     Straat is breed genoeg veilig verkeer
\n     Straat zonder voetpad, te smal als ook voetgangers plaats krijgen
\n     Woonerf, autoluw, autoloos of enkel plaatselijk verkeer
\n
\n
\n Een gestippelde lijn is een straat waar ook voor fietsers éénrichtingsverkeer geldt.
\n Klik op een straat om meer informatie te zien.\n

Hoe gaan we verder?

\n Verschillende ingrepen kunnen de stad teruggeven aan de inwoners en de stad leefbaarder en levendiger maken.
\n Denk aan:\n
    \n
  • De autovrije zone's uitbreiden
  • \n
  • De binnenstad fietszone maken
  • \n
  • Het aantal woonerven uitbreiden
  • \n
  • Grotere auto's meer belasten - ze nemen immers meer parkeerruimte in.
  • \n
  • Laat toeristen verplicht parkeren onder het zand; een (fiets)taxi kan hen naar hun hotel brengen
  • \n
  • Voorzie in elke straat enkele parkeerplaatsen voor kortparkeren. Zo kunnen leveringen, iemand afzetten,... gebeuren zonder op het voetpad en fietspad te parkeren
  • \n
\"" + }, + "language": [ + "nl" + ], + "maintainer": "", + "icon": "./assets/themes/widths/icon.svg", + "version": "0", + "startLat": 51.20875, + "startLon": 3.22435, + "startZoom": 14, + "widenFactor": 0.05, + "socialImage": "", + "layers": [ + { + "id": "widths", + "name": { + "nl": "Straten met een breedte" + }, + "minzoom": 14, + "overpassTags": { + "and": [ + "width:carriageway~*" + ] + }, + "titleIcons": [], + "title": { + "render": { + "nl": "{name}" + }, + "condition": { + "and": [] + }, + "mappings": [ + { + "if": { + "and": [ + "name=" + ] + }, + "then": { + "nl": "Naamloos segmet" + } + } + ] + }, + "description": {}, + "tagRenderings": [ + { + "render": "Deze straat is {width:carriageway}m breed" + }, + { + "render": "Deze straat heeft {_width:difference}m te weinig:", + "mappings": [ + { + "if": { + "or": [ + "_width:difference~-.*", + "_width:difference=0.0" + ] + }, + "then": "Deze straat is breed genoeg:" + } + ] + }, + { + "render": "{_width:needed:cars}m voor het autoverkeer", + "mappings": [ + { + "if": "oneway=yes", + "then": "{_width:needed:cars}m voor het éénrichtings-autoverkeer" + }, + { + "if": "oneway=no", + "then": "{_width:needed:cars}m voor het tweerichtings-autoverkeer" + } + ] + }, + { + "render": "{_width:needed:parking}m voor het geparkeerde wagens", + "condition": "_width:needed:parking!=0.0" + }, + { + "render": "{_width:needed:cyclists}m voor fietsers", + "mappings": [ + { + "if": "bicycle=use_sidepath", + "then": "Fietsers hebben hier een vrijliggend fietspad en worden dus niet meegerekend" + }, + { + "if": "oneway:bicycle=yes", + "then": "{_width:needed:cyclists}m voor fietsers, die met de rijrichting mee moeten" + } + ] + }, + { + "render": "{_width:needed:pedestrians}m voor voetgangers", + "condition": "_width:needed:pedestrians!=0.0", + "mappings": [ + { + "if": { + "or": [ + "sidewalk=none", + "sidewalk=no" + ] + }, + "then": "{_width:needed:pedestrians}m voor voetgangers: er zijn hier geen voetpaden" + }, + { + "if": { + "or": [ + "sidewalk=left", + "sidewalk=right" + ] + }, + "then": "{_width:needed:pedestrians}m voor voetgangers: er is slechts aan één kant een voetpad" + } + ] + }, + { + "render": "{_width:needed}m nodig in het totaal" + }, + { + "render": "{all_tags()}" + } + ], + "hideUnderlayingFeaturesMinPercentage": 0, + "icon": { + "render": "./assets/themes/widths/icon.svg" + }, + "width": { + "render": "4" + }, + "iconSize": { + "render": "40,40,center" + }, + "color": { + "render": "#00f", + "mappings": [ + { + "if": { + "or": [ + "access=destination", + "highway=living_street", + "highway=pedestrian", + "motor_vehicle=no", + "motor_vehicle=destination" + ] + }, + "then": "lightgrey" + }, + { + "if": "_width:difference~-.*", + "then": "#0f0" + }, + { + "if": { + "and": [ + "_width:difference!~-.*", + "_width:difference:no_pedestrians~-.*" + ] + }, + "then": "orange" + }, + { + "if": "_width:difference!~-.*", + "then": "#f00" + } + ] + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": { + "and": [ + "oneway=yes", + { + "or": [ + "oneway:bicycle=yes", + "oneway:bicycle=" + ] + } + ] + }, + "then": "5 6" + } + ] + }, + "presets": [] + } + ], + "roamingRenderings": [], + "defaultBackgroundId": "Stadia.AlidadeSmoothDark" +} \ No newline at end of file diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 39f12f0..20a12f9 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -13,6 +13,7 @@ import PublicHolidayInput from "../UI/Input/OpeningHours/PublicHolidayInput"; import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig"; import EditableTagRendering from "../UI/Popup/EditableTagRendering"; import {SubstitutedTranslation} from "../UI/SpecialVisualizations"; +import {Utils} from "../Utils"; @@ -323,5 +324,19 @@ new T([ ["OH Parse PH 12:00-17:00", () => { const rules = PublicHolidayInput.LoadValue("PH 12:00-17:00"); equal(rules.mode, " "); + }], + ["Round", () => { + equal(Utils.Round(15), "15.0") + equal(Utils.Round(1), "1.0") + equal(Utils.Round(1.5), "1.5") + equal(Utils.Round(0.5), "0.5") + equal(Utils.Round(1.6), "1.6") + + equal(Utils.Round(-15), "-15.0") + equal(Utils.Round(-1), "-1.0") + equal(Utils.Round(-1.5), "-1.5") + equal(Utils.Round(-0.5), "-0.5") + equal(Utils.Round(-1.6), "-1.6") + }] ]);