| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | import {InputElement} from "./InputElement"; | 
					
						
							|  |  |  | import Loc from "../../Models/Loc"; | 
					
						
							|  |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							|  |  |  | import Minimap from "../Base/Minimap"; | 
					
						
							|  |  |  | import BaseLayer from "../../Models/BaseLayer"; | 
					
						
							|  |  |  | import Combine from "../Base/Combine"; | 
					
						
							|  |  |  | import Svg from "../../Svg"; | 
					
						
							| 
									
										
										
										
											2021-07-14 16:05:50 +02:00
										 |  |  | import State from "../../State"; | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | 
					
						
							|  |  |  | import {GeoOperations} from "../../Logic/GeoOperations"; | 
					
						
							|  |  |  | import ShowDataLayer from "../ShowDataLayer"; | 
					
						
							| 
									
										
										
										
											2021-08-07 23:11:34 +02:00
										 |  |  | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  | import * as L from "leaflet"; | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | export default class LocationInput extends InputElement<Loc> { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     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" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     })); | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined) | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |     private _centerLocation: UIEventSource<Loc>; | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |     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; | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |     constructor(options: { | 
					
						
							| 
									
										
										
										
											2021-07-14 16:05:50 +02:00
										 |  |  |         mapBackground?: UIEventSource<BaseLayer>, | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |         snapTo?: UIEventSource<{ feature: any }[]>, | 
					
						
							|  |  |  |         maxSnapDistance?: number, | 
					
						
							|  |  |  |         snappedPointTags?: any, | 
					
						
							|  |  |  |         requiresSnapping?: boolean, | 
					
						
							|  |  |  |         centerLocation: UIEventSource<Loc>, | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |     }) { | 
					
						
							|  |  |  |         super(); | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |         this._snapTo = options.snapTo?.map(features => features?.filter(feat => feat.feature.geometry.type !== "Point")) | 
					
						
							|  |  |  |         this._maxSnapDistance = options.maxSnapDistance | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |         this._centerLocation = options.centerLocation; | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |         this._snappedPointTags = options.snappedPointTags | 
					
						
							|  |  |  |         if (this._snapTo === undefined) { | 
					
						
							|  |  |  |             this._value = this._centerLocation; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             const self = this; | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             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 { | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |                     lon: lon, lat: lat, zoom: undefined | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.mapBackground = options.mapBackground ?? State.state.backgroundLayer ?? new UIEventSource(AvailableBaseLayers.osmCarto) | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |         this.SetClass("block h-full") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     GetValue(): UIEventSource<Loc> { | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |         return this._value; | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     IsValid(t: Loc): boolean { | 
					
						
							|  |  |  |         return t !== undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected InnerConstructElement(): HTMLElement { | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |             const clickLocation = new UIEventSource<Loc>(undefined); | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             const map = new Minimap( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     location: this._centerLocation, | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |                     background: this.mapBackground, | 
					
						
							|  |  |  |                     attribution: this.mapBackground !== State.state.backgroundLayer, | 
					
						
							|  |  |  |                     lastClickLocation: clickLocation | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |             clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             map.leafletMap.addCallbackAndRunD(leaflet => { | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |                 const bounds = leaflet.getBounds() | 
					
						
							|  |  |  |                 leaflet.setMaxBounds(bounds.pad(0.15)) | 
					
						
							|  |  |  |                 const data = { | 
					
						
							|  |  |  |                     type: "FeatureCollection", | 
					
						
							|  |  |  |                     features: [{ | 
					
						
							|  |  |  |                         "type": "Feature", | 
					
						
							|  |  |  |                         "geometry": { | 
					
						
							|  |  |  |                             "type": "LineString", | 
					
						
							|  |  |  |                             "coordinates": [ | 
					
						
							|  |  |  |                                 [ | 
					
						
							|  |  |  |                                     bounds.getEast(), | 
					
						
							|  |  |  |                                     bounds.getNorth() | 
					
						
							|  |  |  |                                 ], | 
					
						
							|  |  |  |                                 [ | 
					
						
							|  |  |  |                                     bounds.getWest(), | 
					
						
							|  |  |  |                                     bounds.getNorth() | 
					
						
							|  |  |  |                                 ], | 
					
						
							|  |  |  |                                 [ | 
					
						
							|  |  |  |                                     bounds.getWest(), | 
					
						
							|  |  |  |                                     bounds.getSouth() | 
					
						
							|  |  |  |                                 ], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                                 [ | 
					
						
							|  |  |  |                                     bounds.getEast(), | 
					
						
							|  |  |  |                                     bounds.getSouth() | 
					
						
							|  |  |  |                                 ], | 
					
						
							|  |  |  |                                 [ | 
					
						
							|  |  |  |                                     bounds.getEast(), | 
					
						
							|  |  |  |                                     bounds.getNorth() | 
					
						
							|  |  |  |                                 ] | 
					
						
							|  |  |  |                             ] | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     }] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 L.geoJSON(data, { | 
					
						
							|  |  |  |                     style: { | 
					
						
							|  |  |  |                         color: "#f00", | 
					
						
							|  |  |  |                         weight: 2, | 
					
						
							|  |  |  |                         opacity: 0.4 | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }).addTo(leaflet) | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             if (this._snapTo !== undefined) { | 
					
						
							|  |  |  |                 new ShowDataLayer(this._snapTo, map.leafletMap, State.state.layoutToUse, false, false) | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |                 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 | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |             this.mapBackground.map(layer => { | 
					
						
							|  |  |  |                 const leaflet = map.leafletMap.data | 
					
						
							|  |  |  |                 if (leaflet === undefined || layer === undefined) { | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 leaflet.setMaxZoom(layer.max_zoom) | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |                 leaflet.setMinZoom(layer.max_zoom - 2) | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |                 leaflet.setZoom(layer.max_zoom - 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             }, [map.leafletMap]) | 
					
						
							|  |  |  |             return new Combine([ | 
					
						
							|  |  |  |                 new Combine([ | 
					
						
							| 
									
										
										
										
											2021-09-08 01:36:44 +02:00
										 |  |  |                     Svg.move_arrows_ui() | 
					
						
							|  |  |  |                         .SetClass("block relative pointer-events-none") | 
					
						
							|  |  |  |                         .SetStyle("left: -2.5rem; top: -2.5rem; width: 5rem; height: 5rem") | 
					
						
							| 
									
										
										
										
											2021-08-07 21:19:01 +02:00
										 |  |  |                 ]).SetClass("block w-0 h-0 z-10 relative") | 
					
						
							|  |  |  |                     .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%"), | 
					
						
							|  |  |  |                 map | 
					
						
							|  |  |  |                     .SetClass("z-0 relative block w-full h-full bg-gray-100") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ]).ConstructElement(); | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error("Could not generate LocationInputElement:", e) | 
					
						
							|  |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-14 00:17:15 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |