forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						cc22c5b0fb
					
				
					 17 changed files with 537 additions and 134 deletions
				
			
		|  | @ -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; | ||||
|     }  | ||||
|     } | ||||
| } | ||||
|  | @ -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' | ||||
|  |  | |||
							
								
								
									
										7
									
								
								Svg.ts
									
										
									
									
									
								
							
							
						
						
									
										7
									
								
								Svg.ts
									
										
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -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<Loc>; | ||||
|     private _isInited = false; | ||||
|     private _allowMoving: boolean; | ||||
|     private readonly _leafletoptions: any; | ||||
| 
 | ||||
|     constructor(options?: { | ||||
|                     background?: UIEventSource<BaseLayer>, | ||||
|                     location?: UIEventSource<Loc>, | ||||
|                     allowMoving?: boolean | ||||
|                     allowMoving?: boolean, | ||||
|                     leafletOptions?: any | ||||
|                 } | ||||
|     ) { | ||||
|         super() | ||||
|  | @ -28,10 +31,11 @@ export default class Minimap extends BaseUIElement { | |||
|         this._location = options?.location ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1}) | ||||
|         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; | ||||
|  | @ -53,7 +57,7 @@ export default class Minimap extends BaseUIElement { | |||
|         return wrapper; | ||||
| 
 | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     private InitMap() { | ||||
|         if (this._constructedHtmlElement === undefined) { | ||||
|             // This element isn't initialized yet
 | ||||
|  | @ -72,8 +76,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, | ||||
|  | @ -83,9 +87,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]] | ||||
|  |  | |||
							
								
								
									
										185
									
								
								UI/Input/LengthInput.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								UI/Input/LengthInput.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,185 @@ | |||
| import {InputElement} from "./InputElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import DirectionInput from "./DirectionInput"; | ||||
| import {RadioButton} from "./RadioButton"; | ||||
| import {FixedInputElement} from "./FixedInputElement"; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Selects a length after clicking on the minimap, in meters | ||||
|  */ | ||||
| export default class LengthInput extends InputElement<string> { | ||||
|     private readonly _location: UIEventSource<Loc>; | ||||
| 
 | ||||
|     public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     private readonly value: UIEventSource<string>; | ||||
|     private background; | ||||
| 
 | ||||
|     constructor(mapBackground: UIEventSource<any>, | ||||
|                 location: UIEventSource<Loc>, | ||||
|                 value?: UIEventSource<string>) { | ||||
|         super(); | ||||
|         this._location = location; | ||||
|         this.value = value ?? new UIEventSource<string>(undefined); | ||||
|         this.background = mapBackground; | ||||
|         this.SetClass("block") | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<string> { | ||||
|         return this.value; | ||||
|     } | ||||
| 
 | ||||
|     IsValid(str: string): boolean { | ||||
|         const t = Number(str) | ||||
|         return !isNaN(t) && t >= 0 && t <= 360; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const modeElement = new RadioButton([ | ||||
|             new FixedInputElement("Measure", "measure"), | ||||
|             new FixedInputElement("Move", "move") | ||||
|         ]) | ||||
|         // @ts-ignore
 | ||||
|         let map = undefined | ||||
|         if (!Utils.runningFromConsole) { | ||||
|             map = DirectionInput.constructMinimap({ | ||||
|                 background: this.background, | ||||
|                 allowMoving: false, | ||||
|                 location: this._location, | ||||
|                 leafletOptions: { | ||||
|                     tap: true | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         const element = new Combine([ | ||||
|             new Combine([Svg.length_crosshair_svg().SetStyle( | ||||
|                 `position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`) | ||||
|             ]) | ||||
|                 .SetClass("block length-crosshair-svg relative") | ||||
|                 .SetStyle("z-index: 1000; visibility: hidden"), | ||||
|             map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"), | ||||
|         ]) | ||||
|             .SetClass("relative block bg-white border border-black rounded-3xl overflow-hidden") | ||||
|             .ConstructElement() | ||||
| 
 | ||||
| 
 | ||||
|         this.RegisterTriggers(element, map?.leafletMap) | ||||
|         element.style.overflow = "hidden" | ||||
|         element.style.display = "block" | ||||
|          | ||||
|       return element | ||||
|     } | ||||
| 
 | ||||
|     private RegisterTriggers(htmlElement: HTMLElement, leafletMap: UIEventSource<L.Map>) { | ||||
| 
 | ||||
|         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; | ||||
|             } | ||||
| 
 | ||||
|             const rect = htmlElement.getBoundingClientRect(); | ||||
|             // 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 = <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) { | ||||
|                     const first = leaflet.layerPointToLatLng(firstClickXY) | ||||
|                     const last = leaflet.layerPointToLatLng([dx, dy]) | ||||
|                     const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 100000) / 100 | ||||
|                     self.value.setData("" + geoDist) | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         htmlElement.ontouchstart = (ev: TouchEvent) => { | ||||
|             onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
| 
 | ||||
|         htmlElement.ontouchmove = (ev: TouchEvent) => { | ||||
|             onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
| 
 | ||||
|         htmlElement.ontouchend = (ev: TouchEvent) => { | ||||
|             onPosChange(undefined, undefined, false, true); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
| 
 | ||||
|         htmlElement.onmousedown = (ev: MouseEvent) => { | ||||
|             onPosChange(ev.clientX, ev.clientY, true); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
| 
 | ||||
|         htmlElement.onmouseup = (ev) => { | ||||
|             onPosChange(ev.clientX, ev.clientY, false, true); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
| 
 | ||||
|         htmlElement.onmousemove = (ev: MouseEvent) => { | ||||
|             onPosChange(ev.clientX, ev.clientY, false); | ||||
|             ev.preventDefault(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<string>, options?: { | ||||
|         location: [number, number], | ||||
|         mapBackgroundLayer?: UIEventSource<any> | ||||
|         mapBackgroundLayer?: UIEventSource<any>, | ||||
|         args: (string | number | boolean)[] | ||||
|         feature?: any | ||||
|     }) => InputElement<string>, | ||||
| 
 | ||||
|     inputmode?: string | ||||
| } | ||||
| 
 | ||||
| export default class ValidatedTextField { | ||||
| 
 | ||||
|     public static bestLayerAt: (location: UIEventSource<Loc>, preferences: UIEventSource<string[]>) => any | ||||
| 
 | ||||
|     public static tpList: TextFieldDef[] = [ | ||||
|         ValidatedTextField.tp( | ||||
|  | @ -63,6 +67,83 @@ 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<Loc>({ | ||||
|                     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<string[]>(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){ | ||||
|                     const lonlat: [number, number] = [...options.location] | ||||
|                     lonlat.reverse() | ||||
|                     options.location = <[number,number]> GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates | ||||
|                     options.location.reverse() | ||||
|                 } | ||||
|                 options.feature | ||||
|                  | ||||
|                 const location = new UIEventSource<Loc>({ | ||||
|                     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<string[]>(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 +194,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<Loc>({ | ||||
|                     lat: options.location[0], | ||||
|                     lon: options.location[1], | ||||
|                     zoom: 19 | ||||
|                 }),value); | ||||
|             }, | ||||
|             "numeric" | ||||
|         ), | ||||
|         ValidatedTextField.tp( | ||||
|             "float", | ||||
|             "A decimal", | ||||
|  | @ -222,6 +287,7 @@ export default class ValidatedTextField { | |||
|      * {string (typename) --> TextFieldDef} | ||||
|      */ | ||||
|     public static AllTypes = ValidatedTextField.allTypesDict(); | ||||
| 
 | ||||
|     public static InputForType(type: string, options?: { | ||||
|         placeholder?: string | BaseUIElement, | ||||
|         value?: UIEventSource<string>, | ||||
|  | @ -233,7 +299,9 @@ export default class ValidatedTextField { | |||
|         country?: () => string, | ||||
|         location?: [number /*lat*/, number /*lon*/], | ||||
|         mapBackgroundLayer?: UIEventSource<any>, | ||||
|         unit?: Unit | ||||
|         unit?: Unit, | ||||
|         args?: (string | number | boolean)[] // Extra arguments for the inputHelper,
 | ||||
|         feature?: any | ||||
|     }): InputElement<string> { | ||||
|         options = options ?? {}; | ||||
|         options.placeholder = options.placeholder ?? type; | ||||
|  | @ -247,7 +315,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 +336,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 +356,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 +373,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 +398,9 @@ export default class ValidatedTextField { | |||
|                       reformat?: ((s: string, country?: () => string) => string), | ||||
|                       inputHelper?: (value: UIEventSource<string>, options?: { | ||||
|                           location: [number, number], | ||||
|                           mapBackgroundLayer: UIEventSource<any> | ||||
|                           mapBackgroundLayer: UIEventSource<any>, | ||||
|                           args: string[], | ||||
|                           feature: any | ||||
|                       }) => InputElement<string>, | ||||
|                       inputmode?: string): TextFieldDef { | ||||
| 
 | ||||
|  |  | |||
|  | @ -333,12 +333,15 @@ export default class TagRenderingQuestion extends Combine { | |||
|         } | ||||
| 
 | ||||
|         const tagsData = tags.data; | ||||
|         const feature = State.state.allElements.ContainingFeatures.get(tagsData.id) | ||||
|         const input: InputElement<string> = 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); | ||||
|  |  | |||
|  | @ -39,7 +39,8 @@ export default class SpecialVisualizations { | |||
|     static constructMiniMap: (options?: { | ||||
|         background?: UIEventSource<BaseLayer>, | ||||
|         location?: UIEventSource<Loc>, | ||||
|         allowMoving?: boolean | ||||
|         allowMoving?: boolean, | ||||
|         leafletOptions?: any | ||||
|     }) => BaseUIElement; | ||||
|     static constructShowDataLayer: (features: UIEventSource<{ feature: any; freshness: Date }[]>, leafletMap: UIEventSource<any>, layoutToUse: UIEventSource<any>, enablePopups?: boolean, zoomToFeatures?: boolean) => any; | ||||
|     public static specialVisualizations: SpecialVisualization[] = | ||||
|  |  | |||
							
								
								
									
										115
									
								
								assets/svg/length-crosshair.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								assets/svg/length-crosshair.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    version="1.0" | ||||
|    width="859.53607pt" | ||||
|    height="858.4754pt" | ||||
|    viewBox="0 0 859.53607 858.4754" | ||||
|    preserveAspectRatio="xMidYMid meet" | ||||
|    id="svg14" | ||||
|    sodipodi:docname="length-crosshair.svg" | ||||
|    inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> | ||||
|   <defs | ||||
|      id="defs18" /> | ||||
|   <sodipodi:namedview | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1" | ||||
|      objecttolerance="10" | ||||
|      gridtolerance="10" | ||||
|      guidetolerance="10" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="999" | ||||
|      id="namedview16" | ||||
|      showgrid="false" | ||||
|      showguides="true" | ||||
|      inkscape:guide-bbox="true" | ||||
|      inkscape:zoom="0.5" | ||||
|      inkscape:cx="307.56567" | ||||
|      inkscape:cy="-35.669379" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="0" | ||||
|      inkscape:window-maximized="1" | ||||
|      inkscape:current-layer="svg14" | ||||
|      inkscape:snap-smooth-nodes="true" /> | ||||
|   <metadata | ||||
|      id="metadata2"> | ||||
| Created by potrace 1.15, written by Peter Selinger 2001-2017 | ||||
| <rdf:RDF> | ||||
|   <cc:Work | ||||
|      rdf:about=""> | ||||
|     <dc:format>image/svg+xml</dc:format> | ||||
|     <dc:type | ||||
|        rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|     <dc:title /> | ||||
|   </cc:Work> | ||||
| </rdf:RDF> | ||||
| </metadata> | ||||
|   <path | ||||
|      style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:71.99999853,71.99999853;stroke-dashoffset:0;stroke-opacity:1" | ||||
|      id="path816" | ||||
|      transform="rotate(-89.47199)" | ||||
|      sodipodi:type="arc" | ||||
|      sodipodi:cx="-425.24921" | ||||
|      sodipodi:cy="433.71375" | ||||
|      sodipodi:rx="428.34982" | ||||
|      sodipodi:ry="427.81949" | ||||
|      sodipodi:start="0" | ||||
|      sodipodi:end="4.7117019" | ||||
|      sodipodi:open="true" | ||||
|      d="M 3.1006165,433.71375 A 428.34982,427.81949 0 0 1 -425.1511,861.53322 428.34982,427.81949 0 0 1 -853.59898,433.90971 428.34982,427.81949 0 0 1 -425.54352,5.8943576" /> | ||||
|   <path | ||||
|      style="fill:none;stroke:#000000;stroke-width:4.49999991;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||
|      d="m 429.76804,430.08754 0,-429.19968" | ||||
|      id="path820" | ||||
|      inkscape:connector-curvature="0" /> | ||||
|   <path | ||||
|      style="fill:none;stroke:#000000;stroke-width:1.49999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:35.99999926,35.99999926;stroke-dashoffset:0" | ||||
|      d="m 857.58749,429.23771 -855.6389371,0 v 0" | ||||
|      id="path822" | ||||
|      inkscape:connector-curvature="0" /> | ||||
|   <path | ||||
|      inkscape:connector-curvature="0" | ||||
|      id="path814" | ||||
|      d="M 429.76804,857.30628 V 428.78674" | ||||
|      style="fill:none;stroke:#000000;stroke-width:1.49999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:35.99999926,35.99999926;stroke-dashoffset:0" /> | ||||
|   <path | ||||
|      inkscape:connector-curvature="0" | ||||
|      id="path826" | ||||
|      d="M 857.32232,1.0332137 H 1.6833879 v 0" | ||||
|      style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:17.99999963, 17.99999963;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|   <path | ||||
|      inkscape:connector-curvature="0" | ||||
|      id="path828" | ||||
|      d="M 857.58749,858.2377 H 1.9485529 v 0" | ||||
|      style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:8.99999982, 8.99999982;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|   <path | ||||
|      cx="-429.2377" | ||||
|      cy="429.76804" | ||||
|      rx="428.34982" | ||||
|      ry="427.81949" | ||||
|      transform="rotate(-90)" | ||||
|      id="path825" | ||||
|      style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:11.99999975;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | ||||
|   <path | ||||
|      d="M -5.3639221,-424.71887 A 428.34982,427.81949 0 0 1 -429.83855,3.0831087 428.34982,427.81949 0 0 1 -861.99345,-416.97839" | ||||
|      sodipodi:open="true" | ||||
|      sodipodi:end="3.1234988" | ||||
|      sodipodi:start="0" | ||||
|      sodipodi:ry="427.81949" | ||||
|      sodipodi:rx="428.34982" | ||||
|      sodipodi:cy="-424.71887" | ||||
|      sodipodi:cx="-433.71375" | ||||
|      sodipodi:type="arc" | ||||
|      transform="rotate(-179.47199)" | ||||
|      id="path827" | ||||
|      style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 4.7 KiB | 
|  | @ -64,7 +64,13 @@ | |||
|       }, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "render": "Deze straat is <b>{width:carriageway}m</b> breed" | ||||
|           "render": "Deze straat is <b>{width:carriageway}m</b> breed", | ||||
|           "question": "Hoe breed is deze straat?", | ||||
|           "freeform": { | ||||
|             "key": "width:carriageway", | ||||
|             "type": "length", | ||||
|             "helperArgs": [21, "map"] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "render": "Deze straat heeft <span class='alert'>{_width:difference}m</span> te weinig:", | ||||
|  |  | |||
							
								
								
									
										3
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								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<L.Map>, | ||||
|  |  | |||
|  | @ -487,6 +487,11 @@ | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "presets": { | ||||
|             "0": { | ||||
|                 "title": "Обслуживание велосипедов/магазин" | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "defibrillator": { | ||||
|  | @ -1064,6 +1069,7 @@ | |||
|             "1": { | ||||
|                 "question": "Вы хотите добавить описание?" | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|         "name": "Смотровая площадка" | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -122,8 +122,10 @@ | |||
|             "thanksForSharing": "Obrigado por compartilhar!", | ||||
|             "copiedToClipboard": "Link copiado para a área de transferência", | ||||
|             "addToHomeScreen": "<h3>Adicionar à sua tela inicial</h3>Você pode adicionar facilmente este site à tela inicial do smartphone para uma sensação nativa. Clique no botão 'adicionar à tela inicial' na barra de URL para fazer isso.", | ||||
|             "intro": "<h3>Compartilhe este mapa</h3> Compartilhe este mapa copiando o link abaixo e enviando-o para amigos e familiares:" | ||||
|         } | ||||
|             "intro": "<h3>Compartilhe este mapa</h3> Compartilhe este mapa copiando o link abaixo e enviando-o para amigos e familiares:", | ||||
|             "embedIntro": "<h3>Incorpore em seu site</h3>Por favor, incorpore este mapa em seu site.<br>Nós o encorajamos a fazer isso - você nem precisa pedir permissão.<br>É gratuito e sempre será. Quanto mais pessoas usarem isso, mais valioso se tornará." | ||||
|         }, | ||||
|         "aboutMapcomplete": "<h3>Sobre o MapComplete</h3><p>Com o MapComplete, você pode enriquecer o OpenStreetMap com informações sobre um<b>único tema.</b>Responda a algumas perguntas e, em minutos, suas contribuições estarão disponíveis em todo o mundo! O<b>mantenedor do tema</b>define elementos, questões e linguagens para o tema.</p><h3>Saiba mais</h3><p>MapComplete sempre<b>oferece a próxima etapa</b>para saber mais sobre o OpenStreetMap.</p><ul><li>Quando incorporado em um site, o iframe vincula-se a um MapComplete em tela inteira</li><li>A versão em tela inteira oferece informações sobre o OpenStreetMap</li><li>A visualização funciona sem login, mas a edição requer um login do OSM.</li><li>Se você não estiver conectado, será solicitado que você faça o login</li><li>Depois de responder a uma única pergunta, você pode adicionar novos aponta para o mapa </li><li> Depois de um tempo, as tags OSM reais são mostradas, posteriormente vinculadas ao wiki </li></ul><p></p><br><p>Você percebeu<b>um problema</b>? Você tem uma<b>solicitação de recurso </b>? Quer<b>ajudar a traduzir</b>? Acesse <a href=\"https://github.com/pietervdvn/MapComplete\" target=\"_blank\">o código-fonte</a>ou <a href=\"https: //github.com/pietervdvn/MapComplete / issues \" target=\" _ blank \">rastreador de problemas.</a></p><p>Quer ver<b>seu progresso</b>? Siga a contagem de edição em<a href=\"https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222021-01-01%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D\" target=\"_blank\">OsmCha</a>.</p>" | ||||
|     }, | ||||
|     "index": { | ||||
|         "pickTheme": "Escolha um tema abaixo para começar.", | ||||
|  | @ -142,10 +144,13 @@ | |||
|         "no_reviews_yet": "Não há comentários ainda. Seja o primeiro a escrever um e ajude a abrir os dados e os negócios!", | ||||
|         "name_required": "É necessário um nome para exibir e criar comentários", | ||||
|         "title_singular": "Um comentário", | ||||
|         "title": "{count} comentários" | ||||
|         "title": "{count} comentários", | ||||
|         "tos": "Se você criar um comentário, você concorda com <a href=\"https://mangrove.reviews/terms\" target=\"_blank\"> o TOS e a política de privacidade de Mangrove.reviews </a>", | ||||
|         "affiliated_reviewer_warning": "(Revisão de afiliados)" | ||||
|     }, | ||||
|     "favourite": { | ||||
|         "reload": "Recarregar dados", | ||||
|         "panelIntro": "<h3>Seu tema pessoal</h3>Ative suas camadas favoritas de todos os temas oficiais" | ||||
|         "panelIntro": "<h3>Seu tema pessoal</h3>Ative suas camadas favoritas de todos os temas oficiais", | ||||
|         "loginNeeded": "<h3>Entrar</h3> Um layout pessoal está disponível apenas para usuários do OpenStreetMap" | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,27 @@ | |||
|         "opening_hours": { | ||||
|             "question": "Was sind die Öffnungszeiten von {name}?", | ||||
|             "render": "<h3>Öffnungszeiten</h3>{opening_hours_table(opening_hours)}" | ||||
|         }, | ||||
|         "level": { | ||||
|             "mappings": { | ||||
|                 "2": { | ||||
|                     "then": "Ist im ersten Stock" | ||||
|                 }, | ||||
|                 "1": { | ||||
|                     "then": "Ist im Erdgeschoss" | ||||
|                 } | ||||
|             }, | ||||
|             "render": "Befindet sich im {level}ten Stock", | ||||
|             "question": "In welchem Stockwerk befindet sich dieses Objekt?" | ||||
|         }, | ||||
|         "description": { | ||||
|             "question": "Gibt es noch etwas, das die vorhergehenden Fragen nicht abgedeckt haben? Hier wäre Platz dafür.<br/><span style='font-size: small'>Bitte keine bereits erhobenen Informationen.</span>" | ||||
|         }, | ||||
|         "website": { | ||||
|             "question": "Was ist die Website von {name}?" | ||||
|         }, | ||||
|         "email": { | ||||
|             "question": "Was ist die Mail-Adresse von {name}?" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1 +1,30 @@ | |||
| {} | ||||
| { | ||||
|     "undefined": { | ||||
|         "level": { | ||||
|             "render": "Localizado no {level}o andar", | ||||
|             "mappings": { | ||||
|                 "2": { | ||||
|                     "then": "Localizado no primeiro andar" | ||||
|                 }, | ||||
|                 "1": { | ||||
|                     "then": "Localizado no térreo" | ||||
|                 }, | ||||
|                 "0": { | ||||
|                     "then": "Localizado no subsolo" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "opening_hours": { | ||||
|             "question": "Qual o horário de funcionamento de {name}?" | ||||
|         }, | ||||
|         "website": { | ||||
|             "question": "Qual o site de {name}?" | ||||
|         }, | ||||
|         "email": { | ||||
|             "question": "Qual o endereço de e-mail de {name}?" | ||||
|         }, | ||||
|         "phone": { | ||||
|             "question": "Qual o número de telefone de {name}?" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -15,6 +15,20 @@ | |||
|         "opening_hours": { | ||||
|             "question": "Какое время работы у {name}?", | ||||
|             "render": "<h3>Часы работы</h3>{opening_hours_table(opening_hours)}" | ||||
|         }, | ||||
|         "level": { | ||||
|             "mappings": { | ||||
|                 "2": { | ||||
|                     "then": "Расположено на первом этаже" | ||||
|                 }, | ||||
|                 "1": { | ||||
|                     "then": "Расположено на первом этаже" | ||||
|                 }, | ||||
|                 "0": { | ||||
|                     "then": "Расположено под землей" | ||||
|                 } | ||||
|             }, | ||||
|             "render": "Расположено на {level}ом этаже" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										76
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										76
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,76 +0,0 @@ | |||
| import SplitAction from "./Logic/Osm/Actions/SplitAction"; | ||||
| import {GeoOperations} from "./Logic/GeoOperations"; | ||||
| 
 | ||||
| const way = { | ||||
|     "type": "Feature", | ||||
|     "properties": { | ||||
|         "highway": "residential", | ||||
|         "maxweight": "3.5", | ||||
|         "maxweight:conditional": "none @ delivery", | ||||
|         "name": "Silsstraat", | ||||
|         "_last_edit:contributor": "Jorisbo", | ||||
|         "_last_edit:contributor:uid": 1983103, | ||||
|         "_last_edit:changeset": 70963524, | ||||
|         "_last_edit:timestamp": "2019-06-05T18:20:44Z", | ||||
|         "_version_number": 9, | ||||
|         "id": "way/23583625" | ||||
|     }, | ||||
|     "geometry": { | ||||
|         "type": "LineString", | ||||
|         "coordinates": [ | ||||
|             [ | ||||
|                 4.4889691, | ||||
|                 51.2049831 | ||||
|             ], | ||||
|             [ | ||||
|                 4.4895496, | ||||
|                 51.2047718 | ||||
|             ], | ||||
|             [ | ||||
|                 4.48966, | ||||
|                 51.2047147 | ||||
|             ], | ||||
|             [ | ||||
|                 4.4897439, | ||||
|                 51.2046548 | ||||
|             ], | ||||
|             [ | ||||
|                 4.4898162, | ||||
|                 51.2045921 | ||||
|             ], | ||||
|             [ | ||||
|                 4.4902997, | ||||
|                 51.2038418 | ||||
|             ] | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| let splitPoint = { | ||||
|     "type": "Feature", | ||||
|     "properties": {}, | ||||
|     "geometry": { | ||||
|         "type": "Point", | ||||
|         "coordinates": [ | ||||
|             4.490211009979248, | ||||
|             51.2041509326002 | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| let splitClose = { | ||||
|     "type": "Feature", | ||||
|     "properties": {}, | ||||
|     "geometry": { | ||||
|         "type": "Point", | ||||
|         "coordinates": [ | ||||
|             4.489563927054405, | ||||
|             51.2047546593862 | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| // State.state = new State(AllKnownLayouts.allKnownLayouts.get("fietsstraten"));
 | ||||
| // add road to state
 | ||||
| // State.state.allElements.addOrGetElement(way);
 | ||||
| new SplitAction(way).DoSplit([splitPoint, splitClose].map(p => GeoOperations.nearestPoint(way,<[number, number]> p.geometry.coordinates))) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue