From 4fa9159da10c4b214d975740e413321d450a8425 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 20 Jul 2021 01:33:58 +0200 Subject: [PATCH] First working version of a width measurment tool --- Customizations/JSON/TagRenderingConfig.ts | 17 +- Customizations/JSON/TagRenderingConfigJson.ts | 6 + Svg.ts | 2 +- UI/Base/Minimap.ts | 22 ++- UI/Input/LengthInput.ts | 160 +++++++++++++----- UI/Input/ValidatedTextField.ts | 127 ++++++++++---- UI/Popup/TagRenderingQuestion.ts | 5 +- UI/SpecialVisualizations.ts | 3 +- assets/svg/length-crosshair.svg | 44 +++-- assets/themes/widths/width.json | 8 +- index.ts | 3 + test.ts | 13 +- 12 files changed, 300 insertions(+), 110 deletions(-) diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index d3d4404939..7b36dae44b 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -27,7 +27,8 @@ export default class TagRenderingConfig { readonly type: string, readonly addExtraTags: TagsFilter[]; readonly inline: boolean, - readonly default?: string + readonly default?: string, + readonly helperArgs?: (string | number | boolean)[] }; readonly multiAnswer: boolean; @@ -76,8 +77,8 @@ export default class TagRenderingConfig { addExtraTags: json.freeform.addExtraTags?.map((tg, i) => FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [], inline: json.freeform.inline ?? false, - default: json.freeform.default - + default: json.freeform.default, + helperArgs: json.freeform.helperArgs } if (json.freeform["extraTags"] !== undefined) { @@ -336,20 +337,20 @@ export default class TagRenderingConfig { * Note: this might be hidden by conditions */ public hasMinimap(): boolean { - const translations : Translation[]= Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]); + const translations: Translation[] = Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]); for (const translation of translations) { for (const key in translation.translations) { - if(!translation.translations.hasOwnProperty(key)){ + if (!translation.translations.hasOwnProperty(key)) { continue } const template = translation.translations[key] const parts = SubstitutedTranslation.ExtractSpecialComponents(template) - const hasMiniMap = parts.filter(part =>part.special !== undefined ).some(special => special.special.func.funcName === "minimap") - if(hasMiniMap){ + const hasMiniMap = parts.filter(part => part.special !== undefined).some(special => special.special.func.funcName === "minimap") + if (hasMiniMap) { return true; } } } return false; - } + } } \ No newline at end of file diff --git a/Customizations/JSON/TagRenderingConfigJson.ts b/Customizations/JSON/TagRenderingConfigJson.ts index 89871ec748..8438895255 100644 --- a/Customizations/JSON/TagRenderingConfigJson.ts +++ b/Customizations/JSON/TagRenderingConfigJson.ts @@ -30,6 +30,7 @@ export interface TagRenderingConfigJson { * Allow freeform text input from the user */ freeform?: { + /** * If this key is present, then 'render' is used to display the value. * If this is undefined, the rendering is _always_ shown @@ -40,6 +41,11 @@ export interface TagRenderingConfigJson { * See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values */ type?: string, + /** + * Extra parameters to initialize the input helper arguments. + * For semantics, see the 'SpecialInputElements.md' + */ + helperArgs?: (string | number | boolean)[]; /** * If a value is added with the textfield, these extra tag is addded. * Useful to add a 'fixme=freeform textfield used - to be checked' diff --git a/Svg.ts b/Svg.ts index 26c5505ed3..a3fe46cd7d 100644 --- a/Svg.ts +++ b/Svg.ts @@ -184,7 +184,7 @@ export default class Svg { public static layersAdd_svg() { return new Img(Svg.layersAdd, true);} public static layersAdd_ui() { return new FixedUiElement(Svg.layersAdd_img);} - public static length_crosshair = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " + public static length_crosshair = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " public static length_crosshair_img = Img.AsImageElement(Svg.length_crosshair) public static length_crosshair_svg() { return new Img(Svg.length_crosshair, true);} public static length_crosshair_ui() { return new FixedUiElement(Svg.length_crosshair_img);} diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index a7066c9eed..6ebf37a756 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -5,6 +5,7 @@ import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import {Map} from "leaflet"; +import {Utils} from "../../Utils"; export default class Minimap extends BaseUIElement { @@ -15,11 +16,13 @@ export default class Minimap extends BaseUIElement { private readonly _location: UIEventSource; private _isInited = false; private _allowMoving: boolean; + private readonly _leafletoptions: any; constructor(options?: { background?: UIEventSource, location?: UIEventSource, - allowMoving?: boolean + allowMoving?: boolean, + leafletOptions?: any } ) { super() @@ -28,10 +31,11 @@ export default class Minimap extends BaseUIElement { this._location = options?.location ?? new UIEventSource(undefined) this._id = "minimap" + Minimap._nextId; this._allowMoving = options.allowMoving ?? true; + this._leafletoptions = options.leafletOptions ?? {} Minimap._nextId++ } - + protected InnerConstructElement(): HTMLElement { const div = document.createElement("div") div.id = this._id; @@ -52,7 +56,7 @@ export default class Minimap extends BaseUIElement { return wrapper; } - + private InitMap() { if (this._constructedHtmlElement === undefined) { // This element isn't initialized yet @@ -71,8 +75,8 @@ export default class Minimap extends BaseUIElement { const location = this._location; let currentLayer = this._background.data.layer() - const map = L.map(this._id, { - center: [location.data?.lat ?? 0, location.data?.lon ?? 0], + const options = { + center: <[number, number]> [location.data?.lat ?? 0, location.data?.lon ?? 0], zoom: location.data?.zoom ?? 2, layers: [currentLayer], zoomControl: false, @@ -82,9 +86,13 @@ export default class Minimap extends BaseUIElement { doubleClickZoom: this._allowMoving, keyboard: this._allowMoving, touchZoom: this._allowMoving, - // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, + // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, fadeAnimation: this._allowMoving - }); + } + + Utils.Merge(this._leafletoptions, options) + + const map = L.map(this._id, options); map.setMaxBounds( [[-100, -200], [100, 200]] diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts index 82b79ee0f4..ea7530ce3e 100644 --- a/UI/Input/LengthInput.ts +++ b/UI/Input/LengthInput.ts @@ -2,11 +2,12 @@ import {InputElement} from "./InputElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; -import BaseUIElement from "../BaseUIElement"; -import {FixedUiElement} from "../Base/FixedUiElement"; import {Utils} from "../../Utils"; import Loc from "../../Models/Loc"; -import Minimap from "../Base/Minimap"; +import {GeoOperations} from "../../Logic/GeoOperations"; +import DirectionInput from "./DirectionInput"; +import {RadioButton} from "./RadioButton"; +import {FixedInputElement} from "./FixedInputElement"; /** @@ -19,13 +20,15 @@ export default class LengthInput extends InputElement { private readonly value: UIEventSource; private background; - constructor(mapBackground: UIEventSource, + constructor(mapBackground: UIEventSource, location: UIEventSource, value?: UIEventSource) { super(); this._location = location; this.value = value ?? new UIEventSource(undefined); this.background = mapBackground; + this.SetClass("block") + } GetValue(): UIEventSource { @@ -33,83 +36,150 @@ export default class LengthInput extends InputElement { } IsValid(str: string): boolean { - const t = Number(str); + const t = Number(str) return !isNaN(t) && t >= 0 && t <= 360; } protected InnerConstructElement(): HTMLElement { - - let map: BaseUIElement = new FixedUiElement("") + const modeElement = new RadioButton([ + new FixedInputElement("Measure", "measure"), + new FixedInputElement("Move", "move") + ]) + // @ts-ignore + let map = undefined if (!Utils.runningFromConsole) { - map = new Minimap({ + map = DirectionInput.constructMinimap({ background: this.background, - allowMoving: true, - location: this._location + allowMoving: false, + location: this._location, + leafletOptions: { + tap: true + } }) } - const element = new Combine([ - Svg.direction_stroke_svg().SetStyle( - `position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${this.value.data ?? 0}deg);`) - .SetClass("direction-svg relative") - .SetStyle("z-index: 1000"), - map.SetClass("w-full h-full absolute top-0 left-O rounded-full overflow-hidden"), + new Combine([Svg.length_crosshair_ui().SetStyle( + `visibility: hidden; position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`) + ]) + .SetClass("block length-crosshair-svg relative") + .SetStyle("z-index: 1000"), + map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"), ]) - .SetStyle("position:relative;display:block;width: min(100%, 25em); height: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em") + .SetClass("relative block bg-white border border-black rounded-3xl overflow-hidden") .ConstructElement() - this.value.addCallbackAndRunD(rotation => { - const cone = element.getElementsByClassName("direction-svg")[0] as HTMLElement - cone.style.transform = `rotate(${rotation}deg)`; - - }) - - this.RegisterTriggers(element) + this.RegisterTriggers(element, map?.leafletMap) element.style.overflow = "hidden" - - return element; + element.style.display = "block" + + return element } - private RegisterTriggers(htmlElement: HTMLElement) { - const self = this; + private RegisterTriggers(htmlElement: HTMLElement, leafletMap: UIEventSource) { + + let firstClickXY: [number, number] = undefined + let lastClickXY: [number, number] = undefined + const self = this; + + + function onPosChange(x: number, y: number, isDown: boolean, isUp?: boolean) { + if (x === undefined || y === undefined) { + // Touch end + firstClickXY = undefined; + lastClickXY = undefined; + return; + } - function onPosChange(x: number, y: number) { const rect = htmlElement.getBoundingClientRect(); - const dx = -(rect.left + rect.right) / 2 + x; - const dy = (rect.top + rect.bottom) / 2 - y; - const angle = 180 * Math.atan2(dy, dx) / Math.PI; - const angleGeo = Math.floor((450 - angle) % 360); - self.value.setData("" + angleGeo) + // From the central part of location + const dx = x - rect.left; + const dy = y - rect.top; + if (isDown) { + if (lastClickXY === undefined && firstClickXY === undefined) { + firstClickXY = [dx, dy]; + } else if (firstClickXY !== undefined && lastClickXY === undefined) { + lastClickXY = [dx, dy] + } else if (firstClickXY !== undefined && lastClickXY !== undefined) { + // we measure again + firstClickXY = [dx, dy] + lastClickXY = undefined; + } + } + if (isUp) { + const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0])) + if (distance > 15) { + lastClickXY = [dx, dy] + } + + + } else if (lastClickXY !== undefined) { + return; + } + + + const measurementCrosshair = htmlElement.getElementsByClassName("length-crosshair-svg")[0] as HTMLElement + const measurementCrosshairInner: HTMLElement = measurementCrosshair.firstChild + if (firstClickXY === undefined) { + measurementCrosshair.style.visibility = "hidden" + } else { + measurementCrosshair.style.visibility = "unset" + measurementCrosshair.style.left = firstClickXY[0] + "px"; + measurementCrosshair.style.top = firstClickXY[1] + "px" + + const angle = 180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx) / Math.PI; + const angleGeo = (angle + 270) % 360 + measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)`; + + const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0])) + measurementCrosshairInner.style.width = (distance * 2) + "px" + measurementCrosshairInner.style.marginLeft = -distance + "px" + measurementCrosshairInner.style.marginTop = -distance + "px" + + + const leaflet = leafletMap?.data + if (leaflet) { + console.log(firstClickXY, [dx, dy], "pixel origin", leaflet.getPixelOrigin()) + const first = leaflet.layerPointToLatLng(firstClickXY) + const last = leaflet.layerPointToLatLng([dx, dy]) + console.log(first, last) + const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 100000) / 100 + console.log("First", first, "last", last, "d", geoDist) + self.value.setData("" + geoDist) + } + + } + } - htmlElement.ontouchmove = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY); + htmlElement.ontouchstart = (ev: TouchEvent) => { + onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true); ev.preventDefault(); } - htmlElement.ontouchstart = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY); + htmlElement.ontouchmove = (ev: TouchEvent) => { + onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false); + ev.preventDefault(); } - let isDown = false; + htmlElement.ontouchend = (ev: TouchEvent) => { + onPosChange(undefined, undefined, false, true); + ev.preventDefault(); + } htmlElement.onmousedown = (ev: MouseEvent) => { - isDown = true; - onPosChange(ev.clientX, ev.clientY); + onPosChange(ev.clientX, ev.clientY, true); ev.preventDefault(); } htmlElement.onmouseup = (ev) => { - isDown = false; + onPosChange(ev.clientX, ev.clientY, false, true); ev.preventDefault(); } htmlElement.onmousemove = (ev: MouseEvent) => { - if (isDown) { - onPosChange(ev.clientX, ev.clientY); - } + onPosChange(ev.clientX, ev.clientY, false); ev.preventDefault(); } } diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 2eeff8a543..809ff025fd 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -13,6 +13,8 @@ import {Utils} from "../../Utils"; import Loc from "../../Models/Loc"; import {Unit} from "../../Customizations/JSON/Denomination"; import BaseUIElement from "../BaseUIElement"; +import LengthInput from "./LengthInput"; +import {GeoOperations} from "../../Logic/GeoOperations"; interface TextFieldDef { name: string, @@ -21,14 +23,16 @@ interface TextFieldDef { reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { location: [number, number], - mapBackgroundLayer?: UIEventSource + mapBackgroundLayer?: UIEventSource, + args: (string | number | boolean)[] + feature?: any }) => InputElement, - inputmode?: string } export default class ValidatedTextField { + public static bestLayerAt: (location: UIEventSource, preferences: UIEventSource) => any public static tpList: TextFieldDef[] = [ ValidatedTextField.tp( @@ -63,6 +67,79 @@ export default class ValidatedTextField { return [year, month, day].join('-'); }, (value) => new SimpleDatePicker(value)), + ValidatedTextField.tp( + "direction", + "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)", + (str) => { + str = "" + str; + return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360 + }, str => str, + (value, options) => { + const args = options.args ?? [] + let zoom = 19 + if (args[0]) { + zoom = Number(args[0]) + if (isNaN(zoom)) { + throw "Invalid zoom level for argument at 'length'-input" + } + } + const location = new UIEventSource({ + lat: options.location[0], + lon: options.location[1], + zoom: zoom + }) + if (args[1]) { + // We have a prefered map! + options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( + location, new UIEventSource(args[1].split(",")) + ) + } + const di = new DirectionInput(options.mapBackgroundLayer, location, value) + di.SetStyle("height: 20rem;"); + + return di; + }, + "numeric" + ), + ValidatedTextField.tp( + "length", + "A geographical length in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma seperated) ], e.g. `[\"21\", \"map,photo\"]", + (str) => { + const t = Number(str) + return !isNaN(t) + }, + str => str, + (value, options) => { + const args = options.args ?? [] + let zoom = 19 + if (args[0]) { + zoom = Number(args[0]) + if (isNaN(zoom)) { + throw "Invalid zoom level for argument at 'length'-input" + } + } + + // Bit of a hack: we project the centerpoint to the closes point on the road - if available + if(options.feature){ + } + options.feature + + const location = new UIEventSource({ + lat: options.location[0], + lon: options.location[1], + zoom: zoom + }) + if (args[1]) { + // We have a prefered map! + options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( + location, new UIEventSource(args[1].split(",")) + ) + } + const li = new LengthInput(options.mapBackgroundLayer, location, value) + li.SetStyle("height: 20rem;") + return li; + } + ), ValidatedTextField.tp( "wikidata", "A wikidata identifier, e.g. Q42", @@ -113,22 +190,6 @@ export default class ValidatedTextField { undefined, undefined, "numeric"), - ValidatedTextField.tp( - "direction", - "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)", - (str) => { - str = "" + str; - return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360 - }, str => str, - (value, options) => { - return new DirectionInput(options.mapBackgroundLayer , new UIEventSource({ - lat: options.location[0], - lon: options.location[1], - zoom: 19 - }),value); - }, - "numeric" - ), ValidatedTextField.tp( "float", "A decimal", @@ -222,6 +283,7 @@ export default class ValidatedTextField { * {string (typename) --> TextFieldDef} */ public static AllTypes = ValidatedTextField.allTypesDict(); + public static InputForType(type: string, options?: { placeholder?: string | BaseUIElement, value?: UIEventSource, @@ -233,7 +295,9 @@ export default class ValidatedTextField { country?: () => string, location?: [number /*lat*/, number /*lon*/], mapBackgroundLayer?: UIEventSource, - unit?: Unit + unit?: Unit, + args?: (string | number | boolean)[] // Extra arguments for the inputHelper, + feature?: any }): InputElement { options = options ?? {}; options.placeholder = options.placeholder ?? type; @@ -247,7 +311,7 @@ export default class ValidatedTextField { if (str === undefined) { return false; } - if(options.unit) { + if (options.unit) { str = options.unit.stripUnitParts(str) } return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country); @@ -268,7 +332,7 @@ export default class ValidatedTextField { }) } - if(options.unit) { + if (options.unit) { // We need to apply a unit. // This implies: // We have to create a dropdown with applicable denominations, and fuse those values @@ -288,17 +352,16 @@ export default class ValidatedTextField { input, unitDropDown, // combine the value from the textfield and the dropdown into the resulting value that should go into OSM - (text, denom) => denom?.canonicalValue(text, true) ?? undefined, + (text, denom) => denom?.canonicalValue(text, true) ?? undefined, (valueWithDenom: string) => { // Take the value from OSM and feed it into the textfield and the dropdown const withDenom = unit.findDenomination(valueWithDenom); - if(withDenom === undefined) - { + if (withDenom === undefined) { // Not a valid value at all - we give it undefined and leave the details up to the other elements return [undefined, undefined] } const [strippedText, denom] = withDenom - if(strippedText === undefined){ + if (strippedText === undefined) { return [undefined, undefined] } return [strippedText, denom] @@ -306,18 +369,20 @@ export default class ValidatedTextField { ).SetClass("flex") } if (tp.inputHelper) { - const helper = tp.inputHelper(input.GetValue(), { + const helper = tp.inputHelper(input.GetValue(), { location: options.location, - mapBackgroundLayer: options.mapBackgroundLayer - + mapBackgroundLayer: options.mapBackgroundLayer, + args: options.args, + feature: options.feature }) input = new CombinedInputElement(input, helper, (a, _) => a, // We can ignore b, as they are linked earlier a => [a, a] - ); + ); } return input; } + 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 @@ -329,7 +394,9 @@ export default class ValidatedTextField { reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { location: [number, number], - mapBackgroundLayer: UIEventSource + mapBackgroundLayer: UIEventSource, + args: string[], + feature: any }) => InputElement, inputmode?: string): TextFieldDef { diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 20c0b00d21..c723759590 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -330,12 +330,15 @@ export default class TagRenderingQuestion extends Combine { } const tagsData = tags.data; + const feature = State.state.allElements.ContainingFeatures.get(tagsData.id) const input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { isValid: (str) => (str.length <= 255), country: () => tagsData._country, location: [tagsData._lat, tagsData._lon], mapBackgroundLayer: State.state.backgroundLayer, - unit: applicableUnit + unit: applicableUnit, + args: configuration.freeform.helperArgs, + feature: feature }); input.GetValue().setData(tagsData[freeform.key] ?? freeform.default); diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 309060b364..5a38e8184a 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -39,7 +39,8 @@ export default class SpecialVisualizations { static constructMiniMap: (options?: { background?: UIEventSource, location?: UIEventSource, - allowMoving?: boolean + allowMoving?: boolean, + leafletOptions?: any }) => BaseUIElement; static constructShowDataLayer: (features: UIEventSource<{ feature: any; freshness: Date }[]>, leafletMap: UIEventSource, layoutToUse: UIEventSource, enablePopups?: boolean, zoomToFeatures?: boolean) => any; public static specialVisualizations: SpecialVisualization[] = diff --git a/assets/svg/length-crosshair.svg b/assets/svg/length-crosshair.svg index 6db2cf72b3..cb83789fb5 100644 --- a/assets/svg/length-crosshair.svg +++ b/assets/svg/length-crosshair.svg @@ -26,17 +26,17 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1680" - inkscape:window-height="1009" + inkscape:window-width="1920" + inkscape:window-height="999" id="namedview16" showgrid="false" showguides="true" inkscape:guide-bbox="true" - inkscape:zoom="0.25" - inkscape:cx="-448.31847" - inkscape:cy="144.08448" + inkscape:zoom="0.5" + inkscape:cx="108.3764" + inkscape:cy="623.05359" inkscape:window-x="0" - inkscape:window-y="15" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg14" inkscape:snap-smooth-nodes="true" /> @@ -54,20 +54,36 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017 + ry="427.81949" + transform="rotate(-90)" /> + + + diff --git a/assets/themes/widths/width.json b/assets/themes/widths/width.json index 48a1e883a5..298b9a1281 100644 --- a/assets/themes/widths/width.json +++ b/assets/themes/widths/width.json @@ -64,7 +64,13 @@ }, "tagRenderings": [ { - "render": "Deze straat is {width:carriageway}m breed" + "render": "Deze straat is {width:carriageway}m breed", + "question": "Hoe breed is deze straat?", + "freeform": { + "key": "width:carriageway", + "type": "length", + "helperArgs": [21, "map"] + } }, { "render": "Deze straat heeft {_width:difference}m te weinig:", diff --git a/index.ts b/index.ts index 70b06bf305..634ad8533f 100644 --- a/index.ts +++ b/index.ts @@ -19,10 +19,13 @@ import DirectionInput from "./UI/Input/DirectionInput"; import SpecialVisualizations from "./UI/SpecialVisualizations"; import ShowDataLayer from "./UI/ShowDataLayer"; import * as L from "leaflet"; +import ValidatedTextField from "./UI/Input/ValidatedTextField"; +import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); DirectionInput.constructMinimap = options => new Minimap(options) +ValidatedTextField.bestLayerAt = (location, layerPref) => AvailableBaseLayers.SelectBestLayerAccordingTo(location, layerPref) SpecialVisualizations.constructMiniMap = options => new Minimap(options) SpecialVisualizations.constructShowDataLayer = (features: UIEventSource<{ feature: any, freshness: Date }[]>, leafletMap: UIEventSource, diff --git a/test.ts b/test.ts index 23da820f35..21ca94b74b 100644 --- a/test.ts +++ b/test.ts @@ -11,6 +11,7 @@ import LocationInput from "./UI/Input/LocationInput"; import Loc from "./Models/Loc"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; import LengthInput from "./UI/Input/LengthInput"; +import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; /*import ValidatedTextField from "./UI/Input/ValidatedTextField"; import Combine from "./UI/Base/Combine"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; @@ -153,8 +154,16 @@ function TestMiniMap() { } //*/ -const li = new LengthInput() - li.SetStyle("height: 20rem") +const loc = new UIEventSource({ + zoom: 24, + lat: 51.21043, + lon: 3.21389 +}) +const li = new LengthInput( + AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource("map","photo")), + loc +) + li.SetStyle("height: 30rem; background: aliceblue;") .AttachTo("maindiv") new VariableUiElement(li.GetValue().map(v => JSON.stringify(v, null, " "))).AttachTo("extradiv") \ No newline at end of file