Further refactoring

This commit is contained in:
Pieter Vander Vennet 2021-01-02 21:03:40 +01:00
parent e4a2fd1daf
commit 7a7b34b0fa
17 changed files with 350 additions and 297 deletions

View file

@ -1,10 +1,9 @@
import * as editorlayerindex from "../../assets/editor-layer-index.json"
import {BaseLayer} from "../../Models/BaseLayer";
import * as L from "leaflet";
import * as X from "leaflet-providers";
import {UIEventSource} from "../UIEventSource";
import {GeoOperations} from "../GeoOperations";
import {Basemap} from "../Leaflet/Basemap";
import {BaseLayer} from "../../Models/BaseLayer";
import * as X from "leaflet-providers";
import * as L from "leaflet";
import {TileLayer} from "leaflet";
import {Utils} from "../../Utils";
@ -32,12 +31,16 @@ export default class AvailableBaseLayers {
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
public availableEditorLayers: UIEventSource<BaseLayer[]>;
constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>,
currentBackgroundLayer: UIEventSource<BaseLayer>) {
constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>) {
const self = this;
this.availableEditorLayers =
location.map(
(currentLocation) => {
if(currentLocation === undefined){
return AvailableBaseLayers.layerOverview;
}
const currentLayers = self.availableEditorLayers?.data;
const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
@ -57,36 +60,15 @@ export default class AvailableBaseLayers {
});
// Change the baselayer back to OSM if we go out of the current range of the layer
this.availableEditorLayers.addCallbackAndRun(availableLayers => {
const currentLayer = currentBackgroundLayer.data.id;
for (const availableLayer of availableLayers) {
if (availableLayer.id === currentLayer) {
if (availableLayer.max_zoom < location.data.zoom) {
break;
}
if (availableLayer.min_zoom > location.data.zoom) {
break;
}
return; // All good - the current layer still works!
}
}
// Oops, we panned out of range for this layer!
console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard")
layerControl.setData(AvailableBaseLayers.osmCarto);
});
}
private static AvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [AvailableBaseLayers.osmCarto]
const globalLayers = [];
for (const i in AvailableBaseLayers.layerOverview) {
const layer = AvailableBaseLayers.layerOverview[i];
for (const layerOverviewItem of AvailableBaseLayers.layerOverview) {
const layer = layerOverviewItem;
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
globalLayers.push(layer);
continue;

View file

@ -0,0 +1,162 @@
import * as L from "leaflet";
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../../UI/UIElement";
import {Utils} from "../../Utils";
import Svg from "../../Svg";
import {Img} from "../../UI/Img";
export class GeoLocationHandler extends UIElement {
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
private _marker: L.Marker;
private readonly _hasLocation: UIEventSource<boolean>;
private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>;
private readonly _leafletMap: UIEventSource<L.Map>;
private readonly _featureSwitch: UIEventSource<boolean>;
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
leafletMap: UIEventSource<L.Map>,
featureSwitch: UIEventSource<boolean>) {
super(undefined);
this._currentGPSLocation = currentGPSLocation;
this._leafletMap = leafletMap;
this._featureSwitch = featureSwitch;
this._hasLocation = currentGPSLocation.map((location) => location !== undefined);
var self = this;
import("../../vendor/Leaflet.AccuratePosition.js").then(() => {
self.init();
})
}
public init() {
this.ListenTo(this._hasLocation);
this.ListenTo(this._isActive);
this.ListenTo(this._permission);
const self = this;
function onAccuratePositionProgress(e) {
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
}
function onAccuratePositionFound(e) {
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
}
function onAccuratePositionError(e) {
console.log("onerror", e.message);
}
const map = this._leafletMap.data;
map.on('accuratepositionprogress', onAccuratePositionProgress);
map.on('accuratepositionfound', onAccuratePositionFound);
map.on('accuratepositionerror', onAccuratePositionError);
this._currentGPSLocation.addCallback((location) => {
const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
const icon = L.icon(
{
iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)),
iconSize: [40, 40], // size of the icon
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
})
const newMarker = L.marker(location.latlng, {icon: icon});
newMarker.addTo(map);
if (self._marker !== undefined) {
map.removeLayer(self._marker);
}
self._marker = newMarker;
});
navigator?.permissions?.query({name: 'geolocation'})
?.then(function (status) {
console.log("Geolocation is already", status)
if (status.state === "granted") {
self.StartGeolocating();
}
self._permission.setData(status.state);
status.onchange = function () {
self._permission.setData(status.state);
}
});
this.HideOnEmpty(true);
}
InnerRender(): string {
if (!this._featureSwitch.data) {
return "";
}
if (this._hasLocation.data) {
return Svg.crosshair_blue_img;
}
if (this._isActive.data) {
return Svg.crosshair_blue_center_img;
}
return Svg.crosshair_img;
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
const self = this;
htmlElement.onclick = function () {
self.StartGeolocating(19);
}
htmlElement.oncontextmenu = function (e) {
self.StartGeolocating(15);
e.preventDefault();
return false;
}
}
private StartGeolocating(zoomlevel = 19) {
const self = this;
const map : any = this._leafletMap.data;
if (self._permission.data === "denied") {
return "";
}
if (this._currentGPSLocation.data !== undefined) {
this._leafletMap.data.setView(
this._currentGPSLocation.data.latlng, zoomlevel
);
}
console.log("Searching location using GPS")
map.findAccuratePosition({
maxWait: 10000, // defaults to 10000
desiredAccuracy: 50 // defaults to 20
});
if (!self._isActive.data) {
self._isActive.setData(true);
Utils.DoEvery(60000, () => {
if (document.visibilityState !== "visible") {
console.log("Not starting gps: document not visible")
return;
}
map.findAccuratePosition({
maxWait: 10000, // defaults to 10000
desiredAccuracy: 50 // defaults to 20
});
})
}
}
}

View file

@ -0,0 +1,44 @@
import {UIEventSource} from "../UIEventSource";
import {BaseLayer} from "../../Models/BaseLayer";
import AvailableBaseLayers from "./AvailableBaseLayers";
import Loc from "../../Models/Loc";
/**
* Sets the current background layer to a layer that is actually available
*/
export default class LayerResetter {
constructor( currentBackgroundLayer: UIEventSource<BaseLayer>,
location: UIEventSource<Loc>,
availableLayers: UIEventSource<BaseLayer[]>,
defaultLayerId: UIEventSource<string> = undefined) {
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(AvailableBaseLayers.osmCarto.id);
// Change the baselayer back to OSM if we go out of the current range of the layer
availableLayers.addCallbackAndRun(availableLayers => {
let defaultLayer = undefined;
const currentLayer = currentBackgroundLayer.data.id;
for (const availableLayer of availableLayers) {
if (availableLayer.id === currentLayer) {
if (availableLayer.max_zoom < location.data.zoom) {
break;
}
if (availableLayer.min_zoom > location.data.zoom) {
break;
}
if(availableLayer.id === defaultLayerId.data){
defaultLayer = availableLayer;
}
return; // All good - the current layer still works!
}
}
// Oops, we panned out of range for this layer!
console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard")
currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto);
});
}
}

View file

@ -0,0 +1,70 @@
import * as L from "leaflet";
import {UIElement} from "../../UI/UIElement";
import {Img} from "../../UI/Img";
import Svg from "../../Svg";
import {UIEventSource} from "../UIEventSource";
import {FilteredLayer} from "../FilteredLayer";
/**
* The stray-click-hanlders adds a marker to the map if no feature was clicked.
* Shows the given uiToShow-element in the messagebox
*/
export class StrayClickHandler {
private _lastMarker;
private _uiToShow: (() => UIElement);
constructor(
lastClickLocation: UIEventSource<{ lat: number, lon:number }>,
selectedElement: UIEventSource<string>,
filteredLayers: UIEventSource<FilteredLayer[]>,
leafletMap: UIEventSource<L.Map>,
fullscreenMessage: UIEventSource<UIElement>,
uiToShow: (() => UIElement)) {
this._uiToShow = uiToShow;
const self = this;
filteredLayers.data.forEach((filteredLayer) => {
filteredLayer.isDisplayed.addCallback(isEnabled => {
if(isEnabled && self._lastMarker && leafletMap.data !== undefined){
// When a layer is activated, we remove the 'last click location' in order to force the user to reclick
// This reclick might be at a location where a feature now appeared...
leafletMap.data.removeLayer(self._lastMarker);
}
})
})
lastClickLocation.addCallback(function (lastClick) {
selectedElement.setData(undefined);
if (self._lastMarker !== undefined) {
leafletMap.data?.removeLayer(self._lastMarker);
}
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
icon: L.icon({
iconUrl: Img.AsData(Svg.add),
iconSize: [50, 50],
iconAnchor: [25, 50],
popupAnchor: [0, -45]
})
});
const uiElement = uiToShow();
const popup = L.popup().setContent(uiElement.Render());
self._lastMarker.addTo(leafletMap.data);
self._lastMarker.bindPopup(popup);
self._lastMarker.on("click", () => {
fullscreenMessage.setData(self._uiToShow());
uiElement.Update();
});
});
selectedElement.addCallback(() => {
if (self._lastMarker !== undefined) {
leafletMap.data.removeLayer(self._lastMarker);
this._lastMarker = undefined;
}
})
}
}