2021-10-15 05:20:02 +02:00
|
|
|
import UserRelatedState from "./UserRelatedState";
|
|
|
|
import {UIEventSource} from "../UIEventSource";
|
|
|
|
import BaseLayer from "../../Models/BaseLayer";
|
|
|
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|
|
|
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
|
|
|
|
import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter";
|
|
|
|
import Attribution from "../../UI/BigComponents/Attribution";
|
|
|
|
import Minimap, {MinimapObj} from "../../UI/Base/Minimap";
|
|
|
|
import {Tiles} from "../../Models/TileRange";
|
|
|
|
import BaseUIElement from "../../UI/BaseUIElement";
|
|
|
|
import FilteredLayer from "../../Models/FilteredLayer";
|
|
|
|
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
|
|
|
|
import {QueryParameters} from "../Web/QueryParameters";
|
|
|
|
import * as personal from "../../assets/themes/personal/personal.json";
|
|
|
|
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
|
|
|
|
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer";
|
2021-11-08 02:36:01 +01:00
|
|
|
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource";
|
|
|
|
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource";
|
2021-10-15 05:20:02 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains all the leaflet-map related state
|
|
|
|
*/
|
|
|
|
export default class MapState extends UserRelatedState {
|
|
|
|
|
|
|
|
/**
|
|
|
|
The leaflet instance of the big basemap
|
|
|
|
*/
|
2021-10-15 14:52:11 +02:00
|
|
|
public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap");
|
2021-10-15 05:20:02 +02:00
|
|
|
/**
|
|
|
|
* A list of currently available background layers
|
|
|
|
*/
|
|
|
|
public availableBackgroundLayers: UIEventSource<BaseLayer[]>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The current background layer
|
|
|
|
*/
|
|
|
|
public backgroundLayer: UIEventSource<BaseLayer>;
|
|
|
|
/**
|
|
|
|
* Last location where a click was registered
|
|
|
|
*/
|
|
|
|
public readonly LastClickLocation: UIEventSource<{
|
|
|
|
lat: number;
|
|
|
|
lon: number;
|
|
|
|
}> = new UIEventSource<{ lat: number; lon: number }>(undefined);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The location as delivered by the GPS
|
|
|
|
*/
|
2021-11-08 02:36:01 +01:00
|
|
|
public currentUserLocation: FeatureSourceForLayer & Tiled;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* All previously visited points
|
|
|
|
*/
|
|
|
|
public historicalUserLocations: FeatureSourceForLayer & Tiled;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A feature source containing the current home location of the user
|
|
|
|
*/
|
|
|
|
public homeLocation: FeatureSourceForLayer & Tiled
|
2021-10-15 05:20:02 +02:00
|
|
|
|
|
|
|
public readonly mainMapObject: BaseUIElement & MinimapObj;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WHich layers are enabled in the current theme
|
|
|
|
*/
|
|
|
|
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([], "filteredLayers");
|
|
|
|
/**
|
|
|
|
* Which overlays are shown
|
|
|
|
*/
|
|
|
|
public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]
|
|
|
|
|
|
|
|
|
|
|
|
constructor(layoutToUse: LayoutConfig) {
|
|
|
|
super(layoutToUse);
|
|
|
|
|
|
|
|
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl);
|
|
|
|
|
|
|
|
this.backgroundLayer = this.backgroundLayerId.map(
|
|
|
|
(selectedId: string) => {
|
|
|
|
if (selectedId === undefined) {
|
|
|
|
return AvailableBaseLayers.osmCarto;
|
|
|
|
}
|
|
|
|
|
|
|
|
const available = this.availableBackgroundLayers.data;
|
|
|
|
for (const layer of available) {
|
|
|
|
if (layer.id === selectedId) {
|
|
|
|
return layer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return AvailableBaseLayers.osmCarto;
|
|
|
|
},
|
|
|
|
[this.availableBackgroundLayers],
|
|
|
|
(layer) => layer.id
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Selects a different background layer if the background layer has no coverage at the current location
|
|
|
|
*/
|
|
|
|
new BackgroundLayerResetter(
|
|
|
|
this.backgroundLayer,
|
|
|
|
this.locationControl,
|
|
|
|
this.availableBackgroundLayers,
|
|
|
|
this.layoutToUse.defaultBackgroundId
|
|
|
|
);
|
|
|
|
|
|
|
|
const attr = new Attribution(
|
|
|
|
this.locationControl,
|
|
|
|
this.osmConnection.userDetails,
|
|
|
|
this.layoutToUse,
|
|
|
|
this.currentBounds
|
|
|
|
);
|
|
|
|
|
|
|
|
// Will write into this.leafletMap
|
|
|
|
this.mainMapObject = Minimap.createMiniMap({
|
|
|
|
background: this.backgroundLayer,
|
|
|
|
location: this.locationControl,
|
|
|
|
leafletMap: this.leafletMap,
|
|
|
|
bounds: this.currentBounds,
|
|
|
|
attribution: attr,
|
|
|
|
lastClickLocation: this.LastClickLocation
|
|
|
|
})
|
|
|
|
|
2021-10-15 14:52:11 +02:00
|
|
|
|
2021-10-15 05:20:02 +02:00
|
|
|
this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({
|
|
|
|
config: c,
|
2021-10-23 02:46:37 +02:00
|
|
|
isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, "" + c.defaultState, "Wether or not the overlay " + c.id + " is shown")
|
2021-10-15 05:20:02 +02:00
|
|
|
}))
|
|
|
|
this.filteredLayers = this.InitializeFilteredLayers()
|
|
|
|
|
|
|
|
|
2021-10-15 14:52:11 +02:00
|
|
|
this.lockBounds()
|
|
|
|
this.AddAllOverlaysToMap(this.leafletMap)
|
2021-11-08 02:36:01 +01:00
|
|
|
|
|
|
|
this.initHomeLocation()
|
|
|
|
this.initGpsLocation()
|
2021-10-15 05:20:02 +02:00
|
|
|
}
|
|
|
|
|
2021-11-07 16:34:51 +01:00
|
|
|
public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) {
|
|
|
|
const initialized = new Set()
|
|
|
|
for (const overlayToggle of this.overlayToggles) {
|
|
|
|
new ShowOverlayLayer(overlayToggle.config, leafletMap, overlayToggle.isDisplayed)
|
|
|
|
initialized.add(overlayToggle.config)
|
|
|
|
}
|
2021-10-15 05:20:02 +02:00
|
|
|
|
2021-11-07 16:34:51 +01:00
|
|
|
for (const tileLayerSource of this.layoutToUse.tileLayerSources) {
|
|
|
|
if (initialized.has(tileLayerSource)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
new ShowOverlayLayer(tileLayerSource, leafletMap)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2021-10-15 05:20:02 +02:00
|
|
|
|
|
|
|
private lockBounds() {
|
|
|
|
const layout = this.layoutToUse;
|
|
|
|
if (layout.lockLocation) {
|
|
|
|
if (layout.lockLocation === true) {
|
|
|
|
const tile = Tiles.embedded_tile(
|
|
|
|
layout.startLat,
|
|
|
|
layout.startLon,
|
|
|
|
layout.startZoom - 1
|
|
|
|
);
|
|
|
|
const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y);
|
|
|
|
// We use the bounds to get a sense of distance for this zoom level
|
|
|
|
const latDiff = bounds[0][0] - bounds[1][0];
|
|
|
|
const lonDiff = bounds[0][1] - bounds[1][1];
|
|
|
|
layout.lockLocation = [
|
|
|
|
[layout.startLat - latDiff, layout.startLon - lonDiff],
|
|
|
|
[layout.startLat + latDiff, layout.startLon + lonDiff],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
console.warn("Locking the bounds to ", layout.lockLocation);
|
|
|
|
this.leafletMap.addCallbackAndRunD(map => {
|
|
|
|
// @ts-ignore
|
|
|
|
map.setMaxBounds(layout.lockLocation);
|
|
|
|
map.setMinZoom(layout.startZoom);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-11-08 02:36:01 +01:00
|
|
|
|
|
|
|
private initGpsLocation(){
|
|
|
|
// Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler
|
|
|
|
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location")[0]
|
|
|
|
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
private initHomeLocation() {
|
|
|
|
const empty = []
|
|
|
|
const feature = UIEventSource.ListStabilized(this.osmConnection.userDetails.map(userDetails => {
|
|
|
|
|
|
|
|
if (userDetails === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const home = userDetails.home;
|
|
|
|
if (home === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return [home.lon, home.lat]
|
|
|
|
})).map(homeLonLat => {
|
|
|
|
if (homeLonLat === undefined) {
|
|
|
|
return empty
|
|
|
|
}
|
|
|
|
return [{
|
|
|
|
feature: {
|
|
|
|
"type": "Feature",
|
|
|
|
"properties": {
|
|
|
|
"id":"home",
|
|
|
|
"user:home": "yes",
|
|
|
|
"_lon": homeLonLat[0],
|
|
|
|
"_lat": homeLonLat[1]
|
|
|
|
},
|
|
|
|
"geometry": {
|
|
|
|
"type": "Point",
|
|
|
|
"coordinates": homeLonLat
|
|
|
|
}
|
|
|
|
}, freshness: new Date()
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
|
|
|
|
const flayer = this.filteredLayers.data.filter(l => l.layerDef.id === "home_location")[0]
|
|
|
|
this.homeLocation = new SimpleFeatureSource(flayer, Tiles.tile_index(0, 0, 0), feature)
|
|
|
|
|
|
|
|
}
|
2021-10-15 14:52:11 +02:00
|
|
|
|
2021-10-15 05:20:02 +02:00
|
|
|
private InitializeFilteredLayers() {
|
|
|
|
// Initialize the filtered layers state
|
|
|
|
|
|
|
|
const layoutToUse = this.layoutToUse;
|
|
|
|
const empty = []
|
|
|
|
const flayers: FilteredLayer[] = [];
|
|
|
|
for (const layer of layoutToUse.layers) {
|
|
|
|
let isDisplayed: UIEventSource<boolean>
|
|
|
|
if (layoutToUse.id === personal.id) {
|
|
|
|
isDisplayed = this.osmConnection.GetPreference("personal-theme-layer-" + layer.id + "-enabled")
|
|
|
|
.map(value => value === "yes", [], enabled => {
|
|
|
|
return enabled ? "yes" : "";
|
|
|
|
})
|
|
|
|
} else {
|
2021-10-23 02:46:37 +02:00
|
|
|
isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
2021-10-15 05:20:02 +02:00
|
|
|
"layer-" + layer.id,
|
|
|
|
"true",
|
|
|
|
"Wether or not layer " + layer.id + " is shown"
|
2021-10-23 02:46:37 +02:00
|
|
|
)
|
2021-10-15 05:20:02 +02:00
|
|
|
}
|
|
|
|
const flayer = {
|
|
|
|
isDisplayed: isDisplayed,
|
|
|
|
layerDef: layer,
|
|
|
|
appliedFilters: new UIEventSource<{ filter: FilterConfig, selected: number }[]>([]),
|
|
|
|
};
|
|
|
|
|
|
|
|
if (layer.filters.length > 0) {
|
|
|
|
const filtersPerName = new Map<string, FilterConfig>()
|
|
|
|
layer.filters.forEach(f => filtersPerName.set(f.id, f))
|
|
|
|
const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer")
|
|
|
|
flayer.appliedFilters.map(filters => {
|
|
|
|
filters = filters ?? []
|
|
|
|
return filters.map(f => f.filter.id + "." + f.selected).join(",")
|
|
|
|
}, [], textual => {
|
|
|
|
if (textual.length === 0) {
|
|
|
|
return empty
|
|
|
|
}
|
|
|
|
return textual.split(",").map(part => {
|
|
|
|
const [filterId, selected] = part.split(".");
|
|
|
|
return {filter: filtersPerName.get(filterId), selected: Number(selected)}
|
|
|
|
}).filter(f => f.filter !== undefined && !isNaN(f.selected))
|
|
|
|
}).syncWith(qp, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
flayers.push(flayer);
|
|
|
|
}
|
|
|
|
return new UIEventSource<FilteredLayer[]>(flayers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|