| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  | import {Utils} from "../../Utils"; | 
					
						
							|  |  |  | import BaseUIElement from "../BaseUIElement"; | 
					
						
							|  |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							|  |  |  | import Loc from "../../Models/Loc"; | 
					
						
							|  |  |  | import BaseLayer from "../../Models/BaseLayer"; | 
					
						
							|  |  |  | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | 
					
						
							|  |  |  | import * as L from "leaflet"; | 
					
						
							|  |  |  | import {Map} from "leaflet"; | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | import {BBox} from "../../Logic/BBox"; | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |     private static _nextId = 0; | 
					
						
							|  |  |  |     public readonly leafletMap: UIEventSource<Map> | 
					
						
							|  |  |  |     private readonly _id: string; | 
					
						
							|  |  |  |     private readonly _background: UIEventSource<BaseLayer>; | 
					
						
							|  |  |  |     private readonly _location: UIEventSource<Loc>; | 
					
						
							|  |  |  |     private _isInited = false; | 
					
						
							|  |  |  |     private _allowMoving: boolean; | 
					
						
							|  |  |  |     private readonly _leafletoptions: any; | 
					
						
							|  |  |  |     private readonly _onFullyLoaded: (leaflet: L.Map) => void | 
					
						
							|  |  |  |     private readonly _attribution: BaseUIElement | boolean; | 
					
						
							|  |  |  |     private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>; | 
					
						
							|  |  |  |     private readonly _bounds: UIEventSource<BBox> | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private constructor(options: MinimapOptions) { | 
					
						
							|  |  |  |         super() | 
					
						
							|  |  |  |         options = options ?? {} | 
					
						
							|  |  |  |         this.leafletMap = options.leafletMap ?? new UIEventSource<Map>(undefined) | 
					
						
							|  |  |  |         this._background = options?.background ?? new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto) | 
					
						
							|  |  |  |         this._location = options?.location ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1}) | 
					
						
							|  |  |  |         this._bounds = options?.bounds; | 
					
						
							|  |  |  |         this._id = "minimap" + MinimapImplementation._nextId; | 
					
						
							|  |  |  |         this._allowMoving = options.allowMoving ?? true; | 
					
						
							|  |  |  |         this._leafletoptions = options.leafletOptions ?? {} | 
					
						
							|  |  |  |         this._onFullyLoaded = options.onFullyLoaded | 
					
						
							|  |  |  |         this._attribution = options.attribution | 
					
						
							|  |  |  |         this._lastClickLocation = options.lastClickLocation; | 
					
						
							|  |  |  |         MinimapImplementation._nextId++ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static initialize() { | 
					
						
							|  |  |  |         Minimap.createMiniMap = options => new MinimapImplementation(options) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |     public installBounds(factor: number | BBox, showRange?: boolean) { | 
					
						
							|  |  |  |         this.leafletMap.addCallbackD(leaflet => { | 
					
						
							|  |  |  |             let bounds; | 
					
						
							|  |  |  |             if (typeof factor === "number") { | 
					
						
							|  |  |  |                 bounds = leaflet.getBounds() | 
					
						
							|  |  |  |                 leaflet.setMaxBounds(bounds.pad(factor)) | 
					
						
							| 
									
										
										
										
											2021-09-22 20:44:53 +02:00
										 |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 leaflet.setMaxBounds(factor.toLeaflet()) | 
					
						
							|  |  |  |                 bounds = leaflet.getBounds() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (showRange) { | 
					
						
							|  |  |  |                 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-09-21 02:10:42 +02:00
										 |  |  |     protected InnerConstructElement(): HTMLElement { | 
					
						
							|  |  |  |         const div = document.createElement("div") | 
					
						
							|  |  |  |         div.id = this._id; | 
					
						
							|  |  |  |         div.style.height = "100%" | 
					
						
							|  |  |  |         div.style.width = "100%" | 
					
						
							|  |  |  |         div.style.minWidth = "40px" | 
					
						
							|  |  |  |         div.style.minHeight = "40px" | 
					
						
							|  |  |  |         div.style.position = "relative" | 
					
						
							|  |  |  |         const wrapper = document.createElement("div") | 
					
						
							|  |  |  |         wrapper.appendChild(div) | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         const resizeObserver = new ResizeObserver(_ => { | 
					
						
							| 
									
										
										
										
											2021-09-22 20:44:53 +02:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 self.InitMap(); | 
					
						
							|  |  |  |                 self.leafletMap?.data?.invalidateSize() | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							| 
									
										
										
										
											2021-10-04 00:18:08 +02:00
										 |  |  |                 console.warn("Could not construct a minimap:", e) | 
					
						
							| 
									
										
										
										
											2021-09-22 20:44:53 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         resizeObserver.observe(div); | 
					
						
							|  |  |  |         return wrapper; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private InitMap() { | 
					
						
							|  |  |  |         if (this._constructedHtmlElement === undefined) { | 
					
						
							|  |  |  |             // This element isn't initialized yet
 | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (document.getElementById(this._id) === null) { | 
					
						
							|  |  |  |             // not yet attached, we probably got some other event
 | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this._isInited) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this._isInited = true; | 
					
						
							|  |  |  |         const location = this._location; | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         let currentLayer = this._background.data.layer() | 
					
						
							| 
									
										
										
										
											2021-09-22 20:44:53 +02:00
										 |  |  |         let latLon = <[number, number]>[location.data?.lat ?? 0, location.data?.lon ?? 0] | 
					
						
							|  |  |  |         if(isNaN(latLon[0]) || isNaN(latLon[1])){ | 
					
						
							|  |  |  |             latLon = [0,0] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         const options = { | 
					
						
							| 
									
										
										
										
											2021-09-22 20:44:53 +02:00
										 |  |  |             center: latLon, | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |             zoom: location.data?.zoom ?? 2, | 
					
						
							|  |  |  |             layers: [currentLayer], | 
					
						
							|  |  |  |             zoomControl: false, | 
					
						
							|  |  |  |             attributionControl: this._attribution !== undefined, | 
					
						
							|  |  |  |             dragging: this._allowMoving, | 
					
						
							|  |  |  |             scrollWheelZoom: this._allowMoving, | 
					
						
							|  |  |  |             doubleClickZoom: this._allowMoving, | 
					
						
							|  |  |  |             keyboard: this._allowMoving, | 
					
						
							|  |  |  |             touchZoom: 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); | 
					
						
							|  |  |  |         if (self._onFullyLoaded !== undefined) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             currentLayer.on("load", () => { | 
					
						
							|  |  |  |                 console.log("Fully loaded all tiles!") | 
					
						
							|  |  |  |                 self._onFullyLoaded(map) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Users are not allowed to zoom to the 'copies' on the left and the right, stuff goes wrong then
 | 
					
						
							|  |  |  |         // We give a bit of leeway for people on the edges
 | 
					
						
							|  |  |  |         // Also see: https://www.reddit.com/r/openstreetmap/comments/ih4zzc/mapcomplete_a_new_easytouse_editor/g31ubyv/
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         map.setMaxBounds( | 
					
						
							|  |  |  |             [[-100, -200], [100, 200]] | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this._attribution !== undefined) { | 
					
						
							|  |  |  |             if (this._attribution === true) { | 
					
						
							|  |  |  |                 map.attributionControl.setPrefix(false) | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 map.attributionControl.setPrefix( | 
					
						
							|  |  |  |                     "<span id='leaflet-attribution'></span>"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._background.addCallbackAndRun(layer => { | 
					
						
							|  |  |  |             const newLayer = layer.layer() | 
					
						
							|  |  |  |             if (currentLayer !== undefined) { | 
					
						
							|  |  |  |                 map.removeLayer(currentLayer); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             currentLayer = newLayer; | 
					
						
							|  |  |  |             if (self._onFullyLoaded !== undefined) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 currentLayer.on("load", () => { | 
					
						
							|  |  |  |                     console.log("Fully loaded all tiles!") | 
					
						
							|  |  |  |                     self._onFullyLoaded(map) | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             map.addLayer(newLayer); | 
					
						
							|  |  |  |             map.setMaxZoom(layer.max_zoom ?? map.getMaxZoom()) | 
					
						
							|  |  |  |             if (self._attribution !== true && self._attribution !== false) { | 
					
						
							|  |  |  |                 self._attribution?.AttachTo('leaflet-attribution') | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let isRecursing = false; | 
					
						
							|  |  |  |         map.on("moveend", function () { | 
					
						
							|  |  |  |             if (isRecursing) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (map.getZoom() === location.data.zoom && | 
					
						
							|  |  |  |                 map.getCenter().lat === location.data.lat && | 
					
						
							|  |  |  |                 map.getCenter().lng === location.data.lon) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             location.data.zoom = map.getZoom(); | 
					
						
							|  |  |  |             location.data.lat = map.getCenter().lat; | 
					
						
							|  |  |  |             location.data.lon = map.getCenter().lng; | 
					
						
							|  |  |  |             isRecursing = true; | 
					
						
							|  |  |  |             location.ping(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (self._bounds !== undefined) { | 
					
						
							|  |  |  |                 self._bounds.setData(BBox.fromLeafletBounds(map.getBounds())) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             isRecursing = false; // This is ugly, I know
 | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         location.addCallback(loc => { | 
					
						
							|  |  |  |             const mapLoc = map.getCenter() | 
					
						
							|  |  |  |             const dlat = Math.abs(loc.lat - mapLoc[0]) | 
					
						
							|  |  |  |             const dlon = Math.abs(loc.lon - mapLoc[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             map.setView([loc.lat, loc.lon], loc.zoom) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         location.map(loc => loc.zoom) | 
					
						
							|  |  |  |             .addCallback(zoom => { | 
					
						
							|  |  |  |                 if (Math.abs(map.getZoom() - zoom) > 0.1) { | 
					
						
							|  |  |  |                     map.setZoom(zoom, {}); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (self._bounds !== undefined) { | 
					
						
							|  |  |  |             self._bounds.setData(BBox.fromLeafletBounds(map.getBounds())) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this._lastClickLocation) { | 
					
						
							|  |  |  |             const lastClickLocation = this._lastClickLocation | 
					
						
							|  |  |  |             map.on("click", function (e) { | 
					
						
							|  |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng}) | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             map.on("contextmenu", function (e) { | 
					
						
							|  |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng}); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.leafletMap.setData(map) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |