forked from MapComplete/MapComplete
		
	First working version of snapping to already existing ways from the add-UI (still too slow though), partial fix of #436
This commit is contained in:
		
							parent
							
								
									bf2d634208
								
							
						
					
					
						commit
						0a01561d37
					
				
					 15 changed files with 460 additions and 143 deletions
				
			
		|  | @ -14,11 +14,11 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | ||||||
| import SourceConfig from "./SourceConfig"; | import SourceConfig from "./SourceConfig"; | ||||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; |  | ||||||
| import BaseUIElement from "../../UI/BaseUIElement"; | import BaseUIElement from "../../UI/BaseUIElement"; | ||||||
| import {Unit} from "./Denomination"; | import {Unit} from "./Denomination"; | ||||||
| import DeleteConfig from "./DeleteConfig"; | import DeleteConfig from "./DeleteConfig"; | ||||||
| import FilterConfig from "./FilterConfig"; | import FilterConfig from "./FilterConfig"; | ||||||
|  | import PresetConfig from "./PresetConfig"; | ||||||
| 
 | 
 | ||||||
| export default class LayerConfig { | export default class LayerConfig { | ||||||
|     static WAYHANDLING_DEFAULT = 0; |     static WAYHANDLING_DEFAULT = 0; | ||||||
|  | @ -51,12 +51,7 @@ export default class LayerConfig { | ||||||
|     public readonly deletion: DeleteConfig | null; |     public readonly deletion: DeleteConfig | null; | ||||||
|     public readonly allowSplit: boolean |     public readonly allowSplit: boolean | ||||||
| 
 | 
 | ||||||
|     presets: { |     presets: PresetConfig[]; | ||||||
|         title: Translation, |  | ||||||
|         tags: Tag[], |  | ||||||
|         description?: Translation, |  | ||||||
|         preciseInput?: { preferredBackground?: string } |  | ||||||
|     }[]; |  | ||||||
| 
 | 
 | ||||||
|     tagRenderings: TagRenderingConfig[]; |     tagRenderings: TagRenderingConfig[]; | ||||||
|     filters: FilterConfig[]; |     filters: FilterConfig[]; | ||||||
|  | @ -149,17 +144,41 @@ export default class LayerConfig { | ||||||
|         this.minzoomVisible = json.minzoomVisible ?? this.minzoom; |         this.minzoomVisible = json.minzoomVisible ?? this.minzoom; | ||||||
|         this.wayHandling = json.wayHandling ?? 0; |         this.wayHandling = json.wayHandling ?? 0; | ||||||
|         this.presets = (json.presets ?? []).map((pr, i) => { |         this.presets = (json.presets ?? []).map((pr, i) => { | ||||||
|  |              | ||||||
|  |             let preciseInput = undefined; | ||||||
|  |             if(pr.preciseInput !== undefined){ | ||||||
|                 if (pr.preciseInput === true) { |                 if (pr.preciseInput === true) { | ||||||
|                     pr.preciseInput = { |                     pr.preciseInput = { | ||||||
|                         preferredBackground: undefined |                         preferredBackground: undefined | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             return { |                 let snapToLayers: string[]; | ||||||
|  |                 if (typeof pr.preciseInput.snapToLayer === "string") { | ||||||
|  |                     snapToLayers = [pr.preciseInput.snapToLayer] | ||||||
|  |                 } else { | ||||||
|  |                     snapToLayers = pr.preciseInput.snapToLayer | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 let preferredBackground : string[] | ||||||
|  |                 if (typeof pr.preciseInput.preferredBackground === "string") { | ||||||
|  |                     preferredBackground = [pr.preciseInput.preferredBackground] | ||||||
|  |                 } else { | ||||||
|  |                     preferredBackground = pr.preciseInput.preferredBackground | ||||||
|  |                 } | ||||||
|  |                 preciseInput = { | ||||||
|  |                     preferredBackground: preferredBackground, | ||||||
|  |                     snapToLayers: snapToLayers, | ||||||
|  |                     maxSnapDistance: pr.preciseInput.maxSnapDistance ?? 10 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |           | ||||||
|  |             const config : PresetConfig= { | ||||||
|                 title: Translations.T(pr.title, `${context}.presets[${i}].title`), |                 title: Translations.T(pr.title, `${context}.presets[${i}].title`), | ||||||
|                 tags: pr.tags.map((t) => FromJSON.SimpleTag(t)), |                 tags: pr.tags.map((t) => FromJSON.SimpleTag(t)), | ||||||
|                 description: Translations.T(pr.description, `${context}.presets[${i}].description`), |                 description: Translations.T(pr.description, `${context}.presets[${i}].description`), | ||||||
|                 preciseInput: pr.preciseInput |                 preciseInput: preciseInput, | ||||||
|             } |             } | ||||||
|  |             return config; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         /** Given a key, gets the corresponding property from the json (or the default if not found |         /** Given a key, gets the corresponding property from the json (or the default if not found | ||||||
|  | @ -407,12 +426,15 @@ export default class LayerConfig { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         function render(tr: TagRenderingConfig, deflt?: string) { |         function render(tr: TagRenderingConfig, deflt?: string) { | ||||||
|  |             if(tags === undefined){ | ||||||
|  |                 return deflt | ||||||
|  |             } | ||||||
|             const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; |             const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; | ||||||
|             return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); |             return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const iconSize = render(this.iconSize, "40,40,center").split(","); |         const iconSize = render(this.iconSize, "40,40,center").split(","); | ||||||
|         const dashArray = render(this.dashArray).split(" ").map(Number); |         const dashArray = render(this.dashArray)?.split(" ")?.map(Number); | ||||||
|         let color = render(this.color, "#00f"); |         let color = render(this.color, "#00f"); | ||||||
| 
 | 
 | ||||||
|         if (color.startsWith("--")) { |         if (color.startsWith("--")) { | ||||||
|  | @ -445,8 +467,8 @@ export default class LayerConfig { | ||||||
| 
 | 
 | ||||||
|         const iconUrlStatic = render(this.icon); |         const iconUrlStatic = render(this.icon); | ||||||
|         const self = this; |         const self = this; | ||||||
|         const mappedHtml = tags.map((tgs) => { | 
 | ||||||
|             function genHtmlFromString(sourcePart: string): BaseUIElement { |         function genHtmlFromString(sourcePart: string, rotation: string): BaseUIElement { | ||||||
|             const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; |             const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; | ||||||
|             let html: BaseUIElement = new FixedUiElement( |             let html: BaseUIElement = new FixedUiElement( | ||||||
|                 `<img src="${sourcePart}" style="${style}" />` |                 `<img src="${sourcePart}" style="${style}" />` | ||||||
|  | @ -463,6 +485,8 @@ export default class LayerConfig { | ||||||
|             return html; |             return html; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |         const mappedHtml = tags?.map((tgs) => { | ||||||
|             // What do you mean, 'tgs' is never read?
 |             // What do you mean, 'tgs' is never read?
 | ||||||
|             // It is read implicitly in the 'render' method
 |             // It is read implicitly in the 'render' method
 | ||||||
|             const iconUrl = render(self.icon); |             const iconUrl = render(self.icon); | ||||||
|  | @ -473,7 +497,7 @@ export default class LayerConfig { | ||||||
|                 iconUrl.split(";").filter((prt) => prt != "") |                 iconUrl.split(";").filter((prt) => prt != "") | ||||||
|             ); |             ); | ||||||
|             for (const sourcePart of sourceParts) { |             for (const sourcePart of sourceParts) { | ||||||
|                 htmlParts.push(genHtmlFromString(sourcePart)); |                 htmlParts.push(genHtmlFromString(sourcePart, rotation)); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             let badges = []; |             let badges = []; | ||||||
|  | @ -489,7 +513,7 @@ export default class LayerConfig { | ||||||
|                         .filter((prt) => prt != ""); |                         .filter((prt) => prt != ""); | ||||||
| 
 | 
 | ||||||
|                     for (const badgePartStr of partDefs) { |                     for (const badgePartStr of partDefs) { | ||||||
|                         badgeParts.push(genHtmlFromString(badgePartStr)); |                         badgeParts.push(genHtmlFromString(badgePartStr, "0")); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     const badgeCompound = new Combine(badgeParts).SetStyle( |                     const badgeCompound = new Combine(badgeParts).SetStyle( | ||||||
|  | @ -499,7 +523,7 @@ export default class LayerConfig { | ||||||
|                     badges.push(badgeCompound); |                     badges.push(badgeCompound); | ||||||
|                 } else { |                 } else { | ||||||
|                     htmlParts.push( |                     htmlParts.push( | ||||||
|                         genHtmlFromString(iconOverlay.then.GetRenderValue(tgs).txt) |                         genHtmlFromString(iconOverlay.then.GetRenderValue(tgs).txt, "0") | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -533,7 +557,7 @@ export default class LayerConfig { | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             icon: { |             icon: { | ||||||
|                 html: new VariableUiElement(mappedHtml), |                 html: mappedHtml === undefined ? new FixedUiElement(self.icon.render.txt) : new VariableUiElement(mappedHtml), | ||||||
|                 iconSize: [iconW, iconH], |                 iconSize: [iconW, iconH], | ||||||
|                 iconAnchor: [anchorW, anchorH], |                 iconAnchor: [anchorW, anchorH], | ||||||
|                 popupAnchor: [0, 3 - anchorH], |                 popupAnchor: [0, 3 - anchorH], | ||||||
|  |  | ||||||
|  | @ -226,7 +226,21 @@ export interface LayerConfigJson { | ||||||
|          * If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category. |          * If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category. | ||||||
|          */ |          */ | ||||||
|         preciseInput?: true | { |         preciseInput?: true | { | ||||||
|             preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" | string |             /** | ||||||
|  |              * The type of background picture | ||||||
|  |              */ | ||||||
|  |             preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" | string | string [], | ||||||
|  |             /** | ||||||
|  |              * If specified, these layers will be shown to and the new point will be snapped towards it | ||||||
|  |              */ | ||||||
|  |             snapToLayer?: string | string[], | ||||||
|  |             /** | ||||||
|  |              * If specified, a new point will only be snapped if it is within this range. | ||||||
|  |              * Distance in meter | ||||||
|  |              * | ||||||
|  |              * Default: 10 | ||||||
|  |              */ | ||||||
|  |             maxSnapDistance?: number | ||||||
|         } |         } | ||||||
|     }[], |     }[], | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ export default class LayoutConfig { | ||||||
|         this.defaultBackgroundId = json.defaultBackgroundId; |         this.defaultBackgroundId = json.defaultBackgroundId; | ||||||
|         this.layers = LayoutConfig.ExtractLayers(json, this.units, official, context); |         this.layers = LayoutConfig.ExtractLayers(json, this.units, official, context); | ||||||
| 
 | 
 | ||||||
|         // ALl the layers are constructed, let them share tags in now!
 |         // ALl the layers are constructed, let them share tagRenderings now!
 | ||||||
|         const roaming: { r, source: LayerConfig }[] = [] |         const roaming: { r, source: LayerConfig }[] = [] | ||||||
|         for (const layer of this.layers) { |         for (const layer of this.layers) { | ||||||
|             roaming.push({r: layer.GetRoamingRenderings(), source: layer}); |             roaming.push({r: layer.GetRoamingRenderings(), source: layer}); | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								Customizations/JSON/PresetConfig.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Customizations/JSON/PresetConfig.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | import {Translation} from "../../UI/i18n/Translation"; | ||||||
|  | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
|  | 
 | ||||||
|  | export default interface PresetConfig { | ||||||
|  |     title: Translation, | ||||||
|  |     tags: Tag[], | ||||||
|  |     description?: Translation, | ||||||
|  |     /** | ||||||
|  |      * If precise input is set, then an extra map is shown in which the user can drag the map to the precise location | ||||||
|  |      */ | ||||||
|  |     preciseInput?: { | ||||||
|  |         preferredBackground?: string[], | ||||||
|  |         snapToLayers?: string[], | ||||||
|  |         maxSnapDistance?: number | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -14,7 +14,7 @@ export interface ChangeDescription { | ||||||
|         lat: number, |         lat: number, | ||||||
|         lon: number |         lon: number | ||||||
|     } | { |     } | { | ||||||
|         // Coordinates are only used for rendering
 |         // Coordinates are only used for rendering. They should be lon, lat
 | ||||||
|         locations: [number, number][] |         locations: [number, number][] | ||||||
|         nodes: number[], |         nodes: number[], | ||||||
|     } | { |     } | { | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ import OsmChangeAction from "./OsmChangeAction"; | ||||||
| import {Changes} from "../Changes"; | import {Changes} from "../Changes"; | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | import {ChangeDescription} from "./ChangeDescription"; | ||||||
| import {And} from "../../Tags/And"; | import {And} from "../../Tags/And"; | ||||||
|  | import {OsmWay} from "../OsmObject"; | ||||||
|  | import {GeoOperations} from "../../GeoOperations"; | ||||||
| 
 | 
 | ||||||
| export default class CreateNewNodeAction extends OsmChangeAction { | export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|  | @ -11,12 +13,19 @@ export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
|     private readonly _lon: number; |     private readonly _lon: number; | ||||||
| 
 | 
 | ||||||
|     public newElementId: string = undefined |     public newElementId: string = undefined | ||||||
|  |     private readonly _snapOnto: OsmWay; | ||||||
|  |     private readonly _reusePointDistance: number; | ||||||
| 
 | 
 | ||||||
|     constructor(basicTags: Tag[], lat: number, lon: number) { |     constructor(basicTags: Tag[], lat: number, lon: number, options?: { snapOnto: OsmWay, reusePointWithinMeters?: number }) { | ||||||
|         super() |         super() | ||||||
|         this._basicTags = basicTags; |         this._basicTags = basicTags; | ||||||
|         this._lat = lat; |         this._lat = lat; | ||||||
|         this._lon = lon; |         this._lon = lon; | ||||||
|  |         if(lat === undefined || lon === undefined){ | ||||||
|  |             throw "Lat or lon are undefined!" | ||||||
|  |         } | ||||||
|  |         this._snapOnto = options?.snapOnto; | ||||||
|  |         this._reusePointDistance = options.reusePointWithinMeters ?? 1 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { |     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||||
|  | @ -32,7 +41,7 @@ export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
|             properties[kv.key] = kv.value; |             properties[kv.key] = kv.value; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return [{ |         const newPointChange: ChangeDescription = { | ||||||
|             tags: new And(this._basicTags).asChange(properties), |             tags: new And(this._basicTags).asChange(properties), | ||||||
|             type: "node", |             type: "node", | ||||||
|             id: id, |             id: id, | ||||||
|  | @ -40,8 +49,60 @@ export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
|                 lat: this._lat, |                 lat: this._lat, | ||||||
|                 lon: this._lon |                 lon: this._lon | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |         if (this._snapOnto === undefined) { | ||||||
|  |             return [newPointChange] | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         // Project the point onto the way
 | ||||||
|  | 
 | ||||||
|  |         const geojson = this._snapOnto.asGeoJson() | ||||||
|  |         const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat]) | ||||||
|  |         const index = projected.properties.index | ||||||
|  |         // We check that it isn't close to an already existing point
 | ||||||
|  |         let reusedPointId = undefined; | ||||||
|  |         const prev = <[number, number]>geojson.geometry.coordinates[index] | ||||||
|  |         if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) * 1000 < this._reusePointDistance) { | ||||||
|  |             // We reuse this point instead!
 | ||||||
|  |             reusedPointId = this._snapOnto.nodes[index] | ||||||
|  |         } | ||||||
|  |         const next = <[number, number]>geojson.geometry.coordinates[index + 1] | ||||||
|  |         if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) * 1000 < this._reusePointDistance) { | ||||||
|  |             // We reuse this point instead!
 | ||||||
|  |             reusedPointId = this._snapOnto.nodes[index + 1] | ||||||
|  |         } | ||||||
|  |         if (reusedPointId !== undefined) { | ||||||
|  |             console.log("Reusing an existing point:", reusedPointId) | ||||||
|  |             this.newElementId = "node/" + reusedPointId | ||||||
|  | 
 | ||||||
|  |             return [{ | ||||||
|  |                 tags: new And(this._basicTags).asChange(properties), | ||||||
|  |                 type: "node", | ||||||
|  |                 id: reusedPointId | ||||||
|             }] |             }] | ||||||
|  |         } | ||||||
|          |          | ||||||
|  |         const locations = [...this._snapOnto.coordinates] | ||||||
|  |         locations.forEach(coor => coor.reverse()) | ||||||
|  |         console.log("Locations are: ", locations) | ||||||
|  |         const ids = [...this._snapOnto.nodes] | ||||||
|  |          | ||||||
|  |         locations.splice(index + 1, 0, [this._lon, this._lat]) | ||||||
|  |         ids.splice(index + 1, 0, id) | ||||||
|  |          | ||||||
|  |         // Allright, we have to insert a new point in the way
 | ||||||
|  |         return [ | ||||||
|  |             newPointChange, | ||||||
|  |             { | ||||||
|  |                 type:"way", | ||||||
|  |                 id: this._snapOnto.id, | ||||||
|  |                 changes: { | ||||||
|  |                     locations: locations, | ||||||
|  |                     nodes: ids | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,9 +27,6 @@ export class Changes { | ||||||
|     private readonly previouslyCreated : OsmObject[] = [] |     private readonly previouslyCreated : OsmObject[] = [] | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|        this.isUploading.addCallbackAndRun(uploading => { |  | ||||||
|            console.trace("Is uploading changed:", uploading) |  | ||||||
|        }) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static createChangesetFor(csId: string, |     private static createChangesetFor(csId: string, | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { Utils } from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export default class Constants { | export default class Constants { | ||||||
|      |      | ||||||
|     public static vNumber = "0.9.0-rc0"; |     public static vNumber = "0.9.0-rc2"; | ||||||
| 
 | 
 | ||||||
|     // The user journey states thresholds when a new feature gets unlocked
 |     // The user journey states thresholds when a new feature gets unlocked
 | ||||||
|     public static userJourney = { |     public static userJourney = { | ||||||
|  |  | ||||||
|  | @ -9,19 +9,16 @@ import Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; |  | ||||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import UserDetails from "../../Logic/Osm/OsmConnection"; | import UserDetails from "../../Logic/Osm/OsmConnection"; | ||||||
| import {Translation} from "../i18n/Translation"; |  | ||||||
| import LocationInput from "../Input/LocationInput"; | import LocationInput from "../Input/LocationInput"; | ||||||
| import {InputElement} from "../Input/InputElement"; |  | ||||||
| import Loc from "../../Models/Loc"; |  | ||||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||||
| import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | ||||||
| import Hash from "../../Logic/Web/Hash"; | import PresetConfig from "../../Customizations/JSON/PresetConfig"; | ||||||
|  | import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| * The SimpleAddUI is a single panel, which can have multiple states: | * The SimpleAddUI is a single panel, which can have multiple states: | ||||||
|  | @ -32,17 +29,12 @@ import Hash from "../../Logic/Web/Hash"; | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| /*private*/ | /*private*/ | ||||||
| interface PresetInfo { | interface PresetInfo extends PresetConfig { | ||||||
|     description: string | Translation, |  | ||||||
|     name: string | BaseUIElement, |     name: string | BaseUIElement, | ||||||
|     icon: () => BaseUIElement, |     icon: () => BaseUIElement, | ||||||
|     tags: Tag[], |  | ||||||
|     layerToAddTo: { |     layerToAddTo: { | ||||||
|         layerDef: LayerConfig, |         layerDef: LayerConfig, | ||||||
|         isDisplayed: UIEventSource<boolean> |         isDisplayed: UIEventSource<boolean> | ||||||
|     }, |  | ||||||
|     preciseInput?: { |  | ||||||
|         preferredBackground?: string |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -65,14 +57,10 @@ export default class SimpleAddUI extends Toggle { | ||||||
| 
 | 
 | ||||||
|         const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) |         const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) | ||||||
| 
 | 
 | ||||||
|         const addUi = new VariableUiElement( | 
 | ||||||
|             selectedPreset.map(preset => { |         function createNewPoint(tags: any[], location: { lat: number, lon: number }, snapOntoWay?: OsmWay) { | ||||||
|                     if (preset === undefined) { |             console.trace("Creating a new point") | ||||||
|                         return presetsOverview |             const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {snapOnto: snapOntoWay}) | ||||||
|                     } |  | ||||||
|                     return SimpleAddUI.CreateConfirmButton(preset, |  | ||||||
|                         (tags, location) => { |  | ||||||
|                         const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon) |  | ||||||
|             State.state.changes.applyAction(newElementAction) |             State.state.changes.applyAction(newElementAction) | ||||||
|             selectedPreset.setData(undefined) |             selectedPreset.setData(undefined) | ||||||
|             isShown.setData(false) |             isShown.setData(false) | ||||||
|  | @ -82,7 +70,30 @@ export default class SimpleAddUI extends Toggle { | ||||||
|             console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get( |             console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get( | ||||||
|                 newElementAction.newElementId |                 newElementAction.newElementId | ||||||
|             )) |             )) | ||||||
|                         }, () => { | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const addUi = new VariableUiElement( | ||||||
|  |             selectedPreset.map(preset => { | ||||||
|  |                     if (preset === undefined) { | ||||||
|  |                         return presetsOverview | ||||||
|  |                     } | ||||||
|  |                     return SimpleAddUI.CreateConfirmButton(preset, | ||||||
|  |                         (tags, location, snapOntoWayId?: string) => { | ||||||
|  |                             if (snapOntoWayId === undefined) { | ||||||
|  |                                 createNewPoint(tags, location, undefined) | ||||||
|  |                             } else { | ||||||
|  |                                 OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD(way => { | ||||||
|  |                                     createNewPoint(tags, location,<OsmWay> way) | ||||||
|  |                                     return true; | ||||||
|  |                                 }) | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                         }, | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                         () => { | ||||||
|                             selectedPreset.setData(undefined) |                             selectedPreset.setData(undefined) | ||||||
|                         }) |                         }) | ||||||
|                 } |                 } | ||||||
|  | @ -115,11 +126,11 @@ export default class SimpleAddUI extends Toggle { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private static CreateConfirmButton(preset: PresetInfo, |     private static CreateConfirmButton(preset: PresetInfo, | ||||||
|                                        confirm: (tags: any[], location: { lat: number, lon: number }) => void, |                                        confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void, | ||||||
|                                        cancel: () => void): BaseUIElement { |                                        cancel: () => void): BaseUIElement { | ||||||
| 
 | 
 | ||||||
|         let location = State.state.LastClickLocation; |         let location = State.state.LastClickLocation; | ||||||
|         let preciseInput: InputElement<Loc> = undefined |         let preciseInput: LocationInput = undefined | ||||||
|         if (preset.preciseInput !== undefined) { |         if (preset.preciseInput !== undefined) { | ||||||
|             const locationSrc = new UIEventSource({ |             const locationSrc = new UIEventSource({ | ||||||
|                 lat: location.data.lat, |                 lat: location.data.lat, | ||||||
|  | @ -132,9 +143,22 @@ export default class SimpleAddUI extends Toggle { | ||||||
|                 backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) |                 backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             let features: UIEventSource<{ feature: any }[]> = undefined | ||||||
|  |             if (preset.preciseInput.snapToLayers) { | ||||||
|  |                 // We have to snap to certain layers.
 | ||||||
|  |                 // Lets fetch tehm
 | ||||||
|  |                 const asSet = new Set(preset.preciseInput.snapToLayers) | ||||||
|  |                 features = State.state.featurePipeline.features.map(f => f.filter(feat => asSet.has(feat.feature._matching_layer_id))) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const tags = TagUtils.KVtoProperties(preset.tags ?? []); | ||||||
|  |             console.log("Opening precise input ", preset.preciseInput, "with tags", tags) | ||||||
|             preciseInput = new LocationInput({ |             preciseInput = new LocationInput({ | ||||||
|                 mapBackground: backgroundLayer, |                 mapBackground: backgroundLayer, | ||||||
|                 centerLocation: locationSrc |                 centerLocation: locationSrc, | ||||||
|  |                 snapTo: features, | ||||||
|  |                 snappedPointTags: tags, | ||||||
|  |                 maxSnapDistance: preset.preciseInput.maxSnapDistance | ||||||
| 
 | 
 | ||||||
|             }) |             }) | ||||||
|             preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") |             preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") | ||||||
|  | @ -148,7 +172,7 @@ export default class SimpleAddUI extends Toggle { | ||||||
|             ]).SetClass("flex flex-col") |             ]).SetClass("flex flex-col") | ||||||
|         ).SetClass("font-bold break-words") |         ).SetClass("font-bold break-words") | ||||||
|             .onClick(() => { |             .onClick(() => { | ||||||
|                 confirm(preset.tags, (preciseInput?.GetValue() ?? location).data); |                 confirm(preset.tags, (preciseInput?.GetValue() ?? location).data, preciseInput?.snappedOnto?.data?.properties?.id); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         if (preciseInput !== undefined) { |         if (preciseInput !== undefined) { | ||||||
|  | @ -258,6 +282,7 @@ export default class SimpleAddUI extends Toggle { | ||||||
|                     tags: preset.tags, |                     tags: preset.tags, | ||||||
|                     layerToAddTo: layer, |                     layerToAddTo: layer, | ||||||
|                     name: preset.title, |                     name: preset.title, | ||||||
|  |                     title: preset.title, | ||||||
|                     description: preset.description, |                     description: preset.description, | ||||||
|                     icon: icon, |                     icon: icon, | ||||||
|                     preciseInput: preset.preciseInput |                     preciseInput: preset.preciseInput | ||||||
|  |  | ||||||
|  | @ -6,28 +6,114 @@ import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
|  | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||||
|  | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
|  | import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||||
|  | import ShowDataLayer from "../ShowDataLayer"; | ||||||
| 
 | 
 | ||||||
| export default class LocationInput extends InputElement<Loc> { | export default class LocationInput extends InputElement<Loc> { | ||||||
| 
 | 
 | ||||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); |     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||||
|     private _centerLocation: UIEventSource<Loc>; |     private _centerLocation: UIEventSource<Loc>; | ||||||
|     private readonly mapBackground: UIEventSource<BaseLayer>; |     private readonly mapBackground: UIEventSource<BaseLayer>; | ||||||
|  |     private readonly _snapTo: UIEventSource<{ feature: any }[]> | ||||||
|  |     private readonly _value: UIEventSource<Loc> | ||||||
|  |     private readonly _snappedPoint: UIEventSource<any> | ||||||
|  |     private readonly _maxSnapDistance: number | ||||||
|  |     private readonly _snappedPointTags: any; | ||||||
|  |     public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined) | ||||||
| 
 | 
 | ||||||
|     constructor(options?: { |     constructor(options: { | ||||||
|         mapBackground?: UIEventSource<BaseLayer>, |         mapBackground?: UIEventSource<BaseLayer>, | ||||||
|         centerLocation?: UIEventSource<Loc>, |         snapTo?: UIEventSource<{ feature: any }[]>, | ||||||
|  |         maxSnapDistance?: number, | ||||||
|  |         snappedPointTags?: any, | ||||||
|  |         requiresSnapping?: boolean, | ||||||
|  |         centerLocation: UIEventSource<Loc>, | ||||||
|     }) { |     }) { | ||||||
|         super(); |         super(); | ||||||
|         options = options ?? {} |         this._snapTo = options.snapTo?.map(features => features?.filter(feat => feat.feature.geometry.type !== "Point")) | ||||||
|         options.centerLocation = options.centerLocation ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1}) |         this._maxSnapDistance = options.maxSnapDistance | ||||||
|         this._centerLocation = options.centerLocation; |         this._centerLocation = options.centerLocation; | ||||||
|  |         this._snappedPointTags = options.snappedPointTags | ||||||
|  |         if (this._snapTo === undefined) { | ||||||
|  |             this._value = this._centerLocation; | ||||||
|  |         } else { | ||||||
|  |             const self = this; | ||||||
| 
 | 
 | ||||||
|         this.mapBackground = options.mapBackground ?? State.state.backgroundLayer |             let matching_layer: UIEventSource<string> | ||||||
|  | 
 | ||||||
|  |             if (self._snappedPointTags !== undefined) { | ||||||
|  |                 matching_layer = State.state.layoutToUse.map(layout => { | ||||||
|  | 
 | ||||||
|  |                     for (const layer of layout.layers) { | ||||||
|  |                         if (layer.source.osmTags.matchesProperties(self._snappedPointTags)) { | ||||||
|  |                             return layer.id | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     console.error("No matching layer found for tags ", self._snappedPointTags) | ||||||
|  |                     return "matchpoint" | ||||||
|  |                 }) | ||||||
|  |             } else { | ||||||
|  |                 matching_layer = new UIEventSource<string>("matchpoint") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this._snappedPoint = options.centerLocation.map(loc => { | ||||||
|  |                 if (loc === undefined) { | ||||||
|  |                     return undefined; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // We reproject the location onto every 'snap-to-feature' and select the closest
 | ||||||
|  | 
 | ||||||
|  |                 let min = undefined; | ||||||
|  |                 let matchedWay = undefined; | ||||||
|  |                 for (const feature of self._snapTo.data) { | ||||||
|  |                     const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [loc.lon, loc.lat]) | ||||||
|  |                     if (min === undefined) { | ||||||
|  |                         min = nearestPointOnLine | ||||||
|  |                         matchedWay = feature.feature; | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (min.properties.dist > nearestPointOnLine.properties.dist) { | ||||||
|  |                         min = nearestPointOnLine | ||||||
|  |                         matchedWay = feature.feature; | ||||||
|  | 
 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (min.properties.dist * 1000 > self._maxSnapDistance) { | ||||||
|  |                     if (options.requiresSnapping) { | ||||||
|  |                         return undefined | ||||||
|  |                     } else { | ||||||
|  |                         return { | ||||||
|  |                             "type": "Feature", | ||||||
|  |                             "_matching_layer_id": matching_layer.data, | ||||||
|  |                             "properties": options.snappedPointTags ?? min.properties, | ||||||
|  |                             "geometry": {"type": "Point", "coordinates": [loc.lon, loc.lat]} | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 min._matching_layer_id = matching_layer?.data ?? "matchpoint" | ||||||
|  |                 min.properties = options.snappedPointTags ?? min.properties | ||||||
|  |                 self.snappedOnto.setData(matchedWay) | ||||||
|  |                 return min | ||||||
|  |             }, [this._snapTo]) | ||||||
|  | 
 | ||||||
|  |             this._value = this._snappedPoint.map(f => { | ||||||
|  |                 const [lon, lat] = f.geometry.coordinates; | ||||||
|  |                 return { | ||||||
|  |                     lon: lon, lat: lat, zoom: undefined  | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         this.mapBackground = options.mapBackground ?? State.state.backgroundLayer ?? new UIEventSource(AvailableBaseLayers.osmCarto) | ||||||
|         this.SetClass("block h-full") |         this.SetClass("block h-full") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     GetValue(): UIEventSource<Loc> { |     GetValue(): UIEventSource<Loc> { | ||||||
|         return this._centerLocation; |         return this._value; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     IsValid(t: Loc): boolean { |     IsValid(t: Loc): boolean { | ||||||
|  | @ -35,6 +121,7 @@ export default class LocationInput extends InputElement<Loc> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected InnerConstructElement(): HTMLElement { |     protected InnerConstructElement(): HTMLElement { | ||||||
|  |         try { | ||||||
|             const map = new Minimap( |             const map = new Minimap( | ||||||
|                 { |                 { | ||||||
|                     location: this._centerLocation, |                     location: this._centerLocation, | ||||||
|  | @ -47,8 +134,30 @@ export default class LocationInput extends InputElement<Loc> { | ||||||
|                 ) |                 ) | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|         this.mapBackground.map(layer => { |             if (this._snapTo !== undefined) { | ||||||
|  |                 new ShowDataLayer(this._snapTo, map.leafletMap, State.state.layoutToUse, false, false) | ||||||
| 
 | 
 | ||||||
|  |                 const matchPoint = this._snappedPoint.map(loc => { | ||||||
|  |                     if (loc === undefined) { | ||||||
|  |                         return [] | ||||||
|  |                     } | ||||||
|  |                     return [{feature: loc}]; | ||||||
|  |                 }) | ||||||
|  |                 if (this._snapTo) { | ||||||
|  |                     let layout = LocationInput.matchLayout | ||||||
|  |                     if (this._snappedPointTags !== undefined) { | ||||||
|  |                         layout = State.state.layoutToUse | ||||||
|  |                     } | ||||||
|  |                     new ShowDataLayer( | ||||||
|  |                         matchPoint, | ||||||
|  |                         map.leafletMap, | ||||||
|  |                         layout, | ||||||
|  |                         false, false | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.mapBackground.map(layer => { | ||||||
|                 const leaflet = map.leafletMap.data |                 const leaflet = map.leafletMap.data | ||||||
|                 if (leaflet === undefined || layer === undefined) { |                 if (leaflet === undefined || layer === undefined) { | ||||||
|                     return; |                     return; | ||||||
|  | @ -70,6 +179,30 @@ export default class LocationInput extends InputElement<Loc> { | ||||||
|                     .SetClass("z-0 relative block w-full h-full bg-gray-100") |                     .SetClass("z-0 relative block w-full h-full bg-gray-100") | ||||||
| 
 | 
 | ||||||
|             ]).ConstructElement(); |             ]).ConstructElement(); | ||||||
|  |         } catch (e) { | ||||||
|  |             console.error("Could not generate LocationInputElement:", e) | ||||||
|  |             return undefined; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static readonly matchLayout = new UIEventSource(new LayoutConfig({ | ||||||
|  |         description: "Matchpoint style", | ||||||
|  |         icon: "./assets/svg/crosshair-empty.svg", | ||||||
|  |         id: "matchpoint", | ||||||
|  |         language: ["en"], | ||||||
|  |         layers: [{ | ||||||
|  |             id: "matchpoint", source: { | ||||||
|  |                 osmTags: {and: []} | ||||||
|  |             }, | ||||||
|  |             icon: "./assets/svg/crosshair-empty.svg" | ||||||
|  |         }], | ||||||
|  |         maintainer: "MapComplete", | ||||||
|  |         startLat: 0, | ||||||
|  |         startLon: 0, | ||||||
|  |         startZoom: 0, | ||||||
|  |         title: "Location input", | ||||||
|  |         version: "0" | ||||||
|  | 
 | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -85,8 +85,10 @@ export default class ShowDataLayer { | ||||||
|                     console.error(e) |                     console.error(e) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             if (self._enablePopups) { | ||||||
|                 State.state.selectedElement.ping() |                 State.state.selectedElement.ping() | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         features.addCallback(() => update()); |         features.addCallback(() => update()); | ||||||
|         leafletMap.addCallback(() => update()); |         leafletMap.addCallback(() => update()); | ||||||
|  | @ -106,13 +108,12 @@ export default class ShowDataLayer { | ||||||
|         // We have to convert them to the appropriate icon
 |         // We have to convert them to the appropriate icon
 | ||||||
|         // Click handling is done in the next step
 |         // Click handling is done in the next step
 | ||||||
| 
 | 
 | ||||||
|         const tagSource = State.state.allElements.getEventSourceById(feature.properties.id) |  | ||||||
|         const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; |         const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; | ||||||
| 
 |  | ||||||
|         if (layer === undefined) { |         if (layer === undefined) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         const tagSource = feature.properties.id === undefined ? new UIEventSource<any>(feature.properties) : State.state.allElements.getEventSourceById(feature.properties.id) | ||||||
|         const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); |         const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); | ||||||
|         const baseElement = style.icon.html; |         const baseElement = style.icon.html; | ||||||
|         if (!this._enablePopups) { |         if (!this._enablePopups) { | ||||||
|  |  | ||||||
|  | @ -53,6 +53,11 @@ | ||||||
|       "description": { |       "description": { | ||||||
|         "en": "A bollard in the road", |         "en": "A bollard in the road", | ||||||
|         "nl": "Een paaltje in de weg" |         "nl": "Een paaltje in de weg" | ||||||
|  |       }, | ||||||
|  |       "preciseInput": { | ||||||
|  |         "preferredBackground": ["photo"], | ||||||
|  |         "snapToLayer": "cycleways_and_roads", | ||||||
|  |         "maxSnapDistance": 25 | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  | @ -66,6 +71,11 @@ | ||||||
|       "description": { |       "description": { | ||||||
|         "en": "Cycle barrier, slowing down cyclists", |         "en": "Cycle barrier, slowing down cyclists", | ||||||
|         "nl": "Fietshekjes, voor het afremmen van fietsers" |         "nl": "Fietshekjes, voor het afremmen van fietsers" | ||||||
|  |       }, | ||||||
|  |       "preciseInput": { | ||||||
|  |         "preferredBackground": ["photo"], | ||||||
|  |         "snapToLayer": "cycleways_and_roads", | ||||||
|  |         "maxSnapDistance": 25 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|  |  | ||||||
|  | @ -66,6 +66,11 @@ | ||||||
|       "description": { |       "description": { | ||||||
|         "en": "Crossing for pedestrians and/or cyclists", |         "en": "Crossing for pedestrians and/or cyclists", | ||||||
|         "nl": "Oversteekplaats voor voetgangers en/of fietsers" |         "nl": "Oversteekplaats voor voetgangers en/of fietsers" | ||||||
|  |       }, | ||||||
|  |       "preciseInput": { | ||||||
|  |         "preferredBackground": ["photo"], | ||||||
|  |         "snapToLayer": "cycleways_and_roads", | ||||||
|  |         "maxSnapDistance": 25 | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  | @ -79,6 +84,11 @@ | ||||||
|       "description": { |       "description": { | ||||||
|         "en": "Traffic signal on a road", |         "en": "Traffic signal on a road", | ||||||
|         "nl": "Verkeerslicht op een weg" |         "nl": "Verkeerslicht op een weg" | ||||||
|  |       }, | ||||||
|  |       "preciseInput": { | ||||||
|  |         "preferredBackground": ["photo"], | ||||||
|  |         "snapToLayer": "cycleways_and_roads", | ||||||
|  |         "maxSnapDistance": 25 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|  |  | ||||||
|  | @ -16,14 +16,14 @@ | ||||||
|     "en", |     "en", | ||||||
|     "nl" |     "nl" | ||||||
|   ], |   ], | ||||||
|   "maintainer": "", |   "maintainer": "MapComplete", | ||||||
|   "defaultBackgroundId": "CartoDB.Voyager", |   "defaultBackgroundId": "CartoDB.Voyager", | ||||||
|   "icon": "./assets/themes/cycle_infra/cycle-infra.svg", |   "icon": "./assets/themes/cycle_infra/cycle-infra.svg", | ||||||
|   "version": "0", |   "version": "0", | ||||||
|   "startLat": 51, |   "startLat": 51, | ||||||
|   "startLon": 3.75, |   "startLon": 3.75, | ||||||
|   "startZoom": 11, |   "startZoom": 11, | ||||||
|   "widenFactor": 0, |   "widenFactor": 0.05, | ||||||
|   "socialImage": "./assets/themes/cycle_infra/cycle-infra.svg", |   "socialImage": "./assets/themes/cycle_infra/cycle-infra.svg", | ||||||
|   "enableDownload": true, |   "enableDownload": true, | ||||||
|   "layers": [ |   "layers": [ | ||||||
|  |  | ||||||
							
								
								
									
										66
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										66
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -2,40 +2,49 @@ import {UIEventSource} from "./Logic/UIEventSource"; | ||||||
| import LayoutConfig from "./Customizations/JSON/LayoutConfig"; | import LayoutConfig from "./Customizations/JSON/LayoutConfig"; | ||||||
| import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||||
| import State from "./State"; | import State from "./State"; | ||||||
|  | import LocationInput from "./UI/Input/LocationInput"; | ||||||
|  | import Loc from "./Models/Loc"; | ||||||
|  | import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||||
|  | import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||||
| 
 | 
 | ||||||
| const layout = new UIEventSource<LayoutConfig>(AllKnownLayouts.allKnownLayouts.get("bookcases")) | const layout = new UIEventSource<LayoutConfig>(AllKnownLayouts.allKnownLayouts.get("cycle_infra")) | ||||||
| State.state = new State(layout.data) | State.state = new State(layout.data) | ||||||
| 
 | 
 | ||||||
| const features = new UIEventSource<{ feature: any }[]>([ | const features = new UIEventSource<{ feature: any }[]>([ | ||||||
|     { |     { | ||||||
|         feature: { |         feature: { | ||||||
|             "type": "Feature", |             "type": "Feature", | ||||||
|             "properties": {"amenity": "public_bookcase", "id": "node/123"}, |             "properties": {}, | ||||||
| 
 |  | ||||||
|             id: "node/123", |  | ||||||
|             _matching_layer_id: "public_bookcase", |  | ||||||
|             "geometry": { |             "geometry": { | ||||||
|                 "type": "Point", |                 "type": "LineString", | ||||||
|                 "coordinates": [ |                 "coordinates": [ | ||||||
|                     3.220506906509399, |                     [ | ||||||
|                     51.215009243433094 |                         3.219616413116455, | ||||||
|  |                         51.215315026941276 | ||||||
|  |                     ], | ||||||
|  |                     [ | ||||||
|  |                         3.221080899238586, | ||||||
|  |                         51.21564432998662 | ||||||
|  |                     ] | ||||||
|                 ] |                 ] | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, { |     }, | ||||||
|  |     { | ||||||
|         feature: { |         feature: { | ||||||
|             "type": "Feature", |             "type": "Feature", | ||||||
|             "properties": { |             "properties": {}, | ||||||
|                 amenity: "public_bookcase", |  | ||||||
|                 id: "node/456" |  | ||||||
|             }, |  | ||||||
|             _matching_layer_id: "public_bookcase", |  | ||||||
|             id: "node/456", |  | ||||||
|             "geometry": { |             "geometry": { | ||||||
|                 "type": "Point", |                 "type": "LineString", | ||||||
|                 "coordinates": [ |                 "coordinates": [ | ||||||
|                     3.4243011474609375, |                     [ | ||||||
|                     51.138432319543924 |                         3.220340609550476, | ||||||
|  |                         51.21547967875836 | ||||||
|  |                     ], | ||||||
|  |                     [ | ||||||
|  |                         3.2198095321655273, | ||||||
|  |                         51.216390293480515 | ||||||
|  |                     ] | ||||||
|                 ] |                 ] | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -43,5 +52,22 @@ const features = new UIEventSource<{ feature: any }[]>([ | ||||||
| ]) | ]) | ||||||
| 
 | 
 | ||||||
| features.data.map(f => State.state.allElements.addOrGetElement(f.feature)) | features.data.map(f => State.state.allElements.addOrGetElement(f.feature)) | ||||||
| 
 | const loc = new UIEventSource<Loc>({ | ||||||
| 
 |     zoom: 19, | ||||||
|  |     lat: 51.21547967875836, | ||||||
|  |     lon: 3.220340609550476 | ||||||
|  | }) | ||||||
|  | const li = new LocationInput( | ||||||
|  |     { | ||||||
|  |         mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource<string | string[]>("map")), | ||||||
|  |         snapTo: features, | ||||||
|  |         snappedPointTags: { | ||||||
|  |             "barrier": "cycle_barrier" | ||||||
|  |         }, | ||||||
|  |         maxSnapDistance: 15, | ||||||
|  |         requiresSnapping: false, | ||||||
|  |         centerLocation: loc | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  | li.SetStyle("height: 30rem").AttachTo("maindiv") | ||||||
|  | new VariableUiElement(li.GetValue().map(JSON.stringify)).AttachTo("extradiv") | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue