forked from MapComplete/MapComplete
Further refactoring
This commit is contained in:
parent
e4a2fd1daf
commit
7a7b34b0fa
17 changed files with 350 additions and 297 deletions
|
@ -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;
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import * as L from "leaflet";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import State from "../../State";
|
||||
import {Utils} from "../../Utils";
|
||||
import {Basemap} from "./Basemap";
|
||||
import Svg from "../../Svg";
|
||||
import {Img} from "../../UI/Img";
|
||||
|
||||
|
@ -11,12 +9,20 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
|
||||
private _marker: any;
|
||||
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() {
|
||||
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||
leafletMap: UIEventSource<L.Map>,
|
||||
featureSwitch: UIEventSource<boolean>) {
|
||||
super(undefined);
|
||||
this._hasLocation = State.state.currentGPSLocation.map((location) => location !== 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();
|
||||
|
@ -33,11 +39,11 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
|
||||
function onAccuratePositionProgress(e) {
|
||||
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
}
|
||||
|
||||
function onAccuratePositionFound(e) {
|
||||
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
}
|
||||
|
||||
function onAccuratePositionError(e) {
|
||||
|
@ -45,15 +51,13 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
const bm : Basemap = State.state.bm;
|
||||
const map = bm.map;
|
||||
const map = this._leafletMap.data;
|
||||
map.on('accuratepositionprogress', onAccuratePositionProgress);
|
||||
map.on('accuratepositionfound', onAccuratePositionFound);
|
||||
map.on('accuratepositionerror', onAccuratePositionError);
|
||||
|
||||
|
||||
|
||||
State.state.currentGPSLocation.addCallback((location) => {
|
||||
this._currentGPSLocation.addCallback((location) => {
|
||||
|
||||
const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
|
||||
const icon = L.icon(
|
||||
|
@ -62,7 +66,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
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);
|
||||
|
||||
|
@ -88,10 +92,10 @@ export class GeoLocationHandler extends UIElement {
|
|||
}
|
||||
|
||||
InnerRender(): string {
|
||||
if(!State.state.featureSwitchGeolocation.data){
|
||||
if (!this._featureSwitch.data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
if (this._hasLocation.data) {
|
||||
return Svg.crosshair_blue_img;
|
||||
}
|
||||
|
@ -102,44 +106,6 @@ export class GeoLocationHandler extends UIElement {
|
|||
return Svg.crosshair_img;
|
||||
}
|
||||
|
||||
|
||||
private StartGeolocating(zoomlevel = 19) {
|
||||
const self = this;
|
||||
const map = State.state.bm.map;
|
||||
if (self._permission.data === "denied") {
|
||||
return "";
|
||||
}
|
||||
if (State.state.currentGPSLocation.data !== undefined) {
|
||||
State.state.bm.map.setView(
|
||||
State.state.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
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
|
||||
|
@ -156,4 +122,41 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
44
Logic/Actors/LayerResetter.ts
Normal file
44
Logic/Actors/LayerResetter.ts
Normal 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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import * as L from "leaflet";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import State from "../../State";
|
||||
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.
|
||||
|
@ -13,25 +14,29 @@ export class StrayClickHandler {
|
|||
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;
|
||||
const map = State.state.bm.map;
|
||||
State.state.filteredLayers.data.forEach((filteredLayer) => {
|
||||
filteredLayers.data.forEach((filteredLayer) => {
|
||||
filteredLayer.isDisplayed.addCallback(isEnabled => {
|
||||
if(isEnabled && self._lastMarker){
|
||||
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...
|
||||
map.removeLayer(self._lastMarker);
|
||||
leafletMap.data.removeLayer(self._lastMarker);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
State.state.bm.LastClickLocation.addCallback(function (lastClick) {
|
||||
State.state.selectedElement.setData(undefined);
|
||||
lastClickLocation.addCallback(function (lastClick) {
|
||||
selectedElement.setData(undefined);
|
||||
|
||||
if (self._lastMarker !== undefined) {
|
||||
map.removeLayer(self._lastMarker);
|
||||
leafletMap.data?.removeLayer(self._lastMarker);
|
||||
}
|
||||
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
||||
icon: L.icon({
|
||||
|
@ -43,18 +48,18 @@ export class StrayClickHandler {
|
|||
});
|
||||
const uiElement = uiToShow();
|
||||
const popup = L.popup().setContent(uiElement.Render());
|
||||
self._lastMarker.addTo(map);
|
||||
self._lastMarker.addTo(leafletMap.data);
|
||||
self._lastMarker.bindPopup(popup);
|
||||
|
||||
self._lastMarker.on("click", () => {
|
||||
State.state.fullScreenMessage.setData(self._uiToShow());
|
||||
fullscreenMessage.setData(self._uiToShow());
|
||||
uiElement.Update();
|
||||
});
|
||||
});
|
||||
|
||||
State.state.selectedElement.addCallback(() => {
|
||||
selectedElement.addCallback(() => {
|
||||
if (self._lastMarker !== undefined) {
|
||||
map.removeLayer(self._lastMarker);
|
||||
leafletMap.data.removeLayer(self._lastMarker);
|
||||
this._lastMarker = undefined;
|
||||
}
|
||||
})
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Keeps track of a dictionary 'elementID' -> element
|
||||
* Keeps track of a dictionary 'elementID' -> UIEventSource<tags>
|
||||
*/
|
||||
import {UIEventSource} from "./UIEventSource";
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export class FilteredLayer {
|
|||
[State.state.locationControl]
|
||||
);
|
||||
this.combinedIsDisplayed.addCallback(function (isDisplayed) {
|
||||
const map = State.state.bm.map;
|
||||
const map = State.state.leafletMap.data;
|
||||
if (self._geolayer !== undefined && self._geolayer !== null) {
|
||||
if (isDisplayed) {
|
||||
self._geolayer.addTo(map);
|
||||
|
@ -116,7 +116,7 @@ export class FilteredLayer {
|
|||
|
||||
if (this._geolayer !== undefined && this._geolayer !== null) {
|
||||
// Remove the old geojson layer from the map - we'll reshow all the elements later on anyway
|
||||
State.state.bm.map.removeLayer(this._geolayer);
|
||||
State.state.leafletMap.data.removeLayer(this._geolayer);
|
||||
}
|
||||
|
||||
// We fetch all the data we have to show:
|
||||
|
@ -200,7 +200,7 @@ export class FilteredLayer {
|
|||
|
||||
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
|
||||
popup.setLatLng({lat: center[1], lng: center[0]});
|
||||
popup.openOn(State.state.bm.map);
|
||||
popup.openOn(State.state.leafletMap.data);
|
||||
State.state.selectedElement.setData(feature);
|
||||
uiElement.Update();
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ export class FilteredLayer {
|
|||
});
|
||||
|
||||
if (this.combinedIsDisplayed.data) {
|
||||
this._geolayer.addTo(State.state.bm.map);
|
||||
this._geolayer.addTo(State.state.leafletMap.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import * as L from "leaflet"
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import {BaseLayer} from "../../Models/BaseLayer";
|
||||
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
|
||||
import Loc from "../../Models/Loc";
|
||||
|
||||
export class Basemap {
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
public readonly map: Map;
|
||||
|
||||
public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
|
||||
|
||||
|
||||
constructor(leafletElementId: string,
|
||||
location: UIEventSource<Loc>,
|
||||
currentLayer: UIEventSource<BaseLayer>,
|
||||
extraAttribution: UIElement) {
|
||||
this.map = L.map(leafletElementId, {
|
||||
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
||||
zoom: location.data.zoom ?? 2,
|
||||
layers: [AvailableBaseLayers.osmCarto.layer],
|
||||
});
|
||||
|
||||
L.control.scale(
|
||||
{
|
||||
position: 'topright',
|
||||
}
|
||||
).addTo(this.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/
|
||||
this.map.setMaxBounds(
|
||||
[[-100, -200], [100, 200]]
|
||||
);
|
||||
this.map.attributionControl.setPrefix(
|
||||
extraAttribution.Render() + " | <a href='https://osm.org'>OpenStreetMap</a>");
|
||||
|
||||
this.map.zoomControl.setPosition("bottomright");
|
||||
const self = this;
|
||||
|
||||
let previousLayer = currentLayer.data;
|
||||
currentLayer.addCallbackAndRun(layer => {
|
||||
if (layer === previousLayer) {
|
||||
return;
|
||||
}
|
||||
if (previousLayer !== undefined) {
|
||||
self.map.removeLayer(previousLayer.layer);
|
||||
}
|
||||
previousLayer = layer;
|
||||
self.map.addLayer(layer.layer);
|
||||
})
|
||||
|
||||
|
||||
this.map.on("moveend", function () {
|
||||
location.data.zoom = self.map.getZoom();
|
||||
location.data.lat = self.map.getCenter().lat;
|
||||
location.data.lon = self.map.getCenter().lng;
|
||||
location.ping();
|
||||
});
|
||||
|
||||
this.map.on("click", function (e) {
|
||||
self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng})
|
||||
});
|
||||
|
||||
this.map.on("contextmenu", function (e) {
|
||||
self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng});
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@ export class Geocoding {
|
|||
static Search(query: string,
|
||||
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
|
||||
onFail: (() => void)) {
|
||||
const b = State.state.bm.map.getBounds();
|
||||
const b = State.state.leafletMap.data.getBounds();
|
||||
console.log(b);
|
||||
$.getJSON(
|
||||
Geocoding.host + "format=json&limit=1&viewbox=" +
|
||||
|
|
|
@ -162,7 +162,7 @@ export class UpdateFromOverpass {
|
|||
return;
|
||||
}
|
||||
|
||||
const bounds = state.bm.map.getBounds();
|
||||
const bounds = state.leafletMap.data.getBounds();
|
||||
|
||||
const diff = state.layoutToUse.data.widenFactor;
|
||||
|
||||
|
@ -196,7 +196,7 @@ export class UpdateFromOverpass {
|
|||
return false;
|
||||
}
|
||||
|
||||
const b = state.bm.map.getBounds();
|
||||
const b = state.leafletMap.data.getBounds();
|
||||
return b.getSouth() >= bounds.south &&
|
||||
b.getNorth() <= bounds.north &&
|
||||
b.getEast() <= bounds.east &&
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue