More refactoring to fix the tests

This commit is contained in:
pietervdvn 2021-10-15 14:52:11 +02:00
parent 71285d34cd
commit b8abbc9505
16 changed files with 507 additions and 418 deletions

View file

@ -1,298 +1,40 @@
import * as editorlayerindex from "../../assets/editor-layer-index.json"
import BaseLayer from "../../Models/BaseLayer";
import * as L from "leaflet";
import {TileLayer} from "leaflet";
import * as X from "leaflet-providers";
import {UIEventSource} from "../UIEventSource";
import {GeoOperations} from "../GeoOperations";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
export interface AvailableBaseLayersObj {
readonly osmCarto: BaseLayer;
layerOverview: BaseLayer[];
AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]>
SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> ;
}
/**
* Calculates which layers are available at the current location
* Changes the basemap
*/
export default class AvailableBaseLayers {
public static layerOverview: BaseLayer[];
public static osmCarto: BaseLayer;
public static osmCarto: BaseLayer =
{
id: "osm",
name: "OpenStreetMap",
layer: () => AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap",
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright",
19,
false, false),
feature: null,
max_zoom: 19,
min_zoom: 0,
isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context)
category: "osmbasedmap"
}
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
public static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
const source = location.map(
(currentLocation) => {
if (currentLocation === undefined) {
return AvailableBaseLayers.layerOverview;
}
const currentLayers = source?.data; // A bit unorthodox - I know
const newLayers = AvailableBaseLayers.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
if (currentLayers === undefined) {
return newLayers;
}
if (newLayers.length !== currentLayers.length) {
return newLayers;
}
for (let i = 0; i < newLayers.length; i++) {
if (newLayers[i].name !== currentLayers[i].name) {
return newLayers;
}
}
return currentLayers;
});
return source;
private static implementation: AvailableBaseLayersObj
static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
return AvailableBaseLayers.implementation.AvailableLayersAt(location);
}
public static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
return AvailableBaseLayers.AvailableLayersAt(location).map(available => {
// First float all 'best layers' to the top
available.sort((a, b) => {
if (a.isBest && b.isBest) {
return 0;
}
if (!a.isBest) {
return 1
}
return -1;
}
)
if (preferedCategory.data === undefined) {
return available[0]
}
let prefered: string []
if (typeof preferedCategory.data === "string") {
prefered = [preferedCategory.data]
} else {
prefered = preferedCategory.data;
}
prefered.reverse();
for (const category of prefered) {
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
available.sort((a, b) => {
if (a.category === category && b.category === category) {
return 0;
}
if (a.category !== category) {
return 1
}
return -1;
}
)
}
return available[0]
})
}
private static CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [AvailableBaseLayers.osmCarto]
const globalLayers = [];
for (const layerOverviewItem of AvailableBaseLayers.layerOverview) {
const layer = layerOverviewItem;
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
globalLayers.push(layer);
continue;
}
if (lon === undefined || lat === undefined) {
continue;
}
if (GeoOperations.inside([lon, lat], layer.feature)) {
availableLayers.push(layer);
}
}
return availableLayers.concat(globalLayers);
}
private static LoadRasterIndex(): BaseLayer[] {
const layers: BaseLayer[] = []
// @ts-ignore
const features = editorlayerindex.features;
for (const i in features) {
const layer = features[i];
const props = layer.properties;
if (props.id === "Bing") {
// Doesnt work
continue;
}
if (props.id === "MAPNIK") {
// Already added by default
continue;
}
if (props.overlay) {
continue;
}
if (props.url.toLowerCase().indexOf("apikey") > 0) {
continue;
}
if (props.max_zoom < 19) {
// We want users to zoom to level 19 when adding a point
// If they are on a layer which hasn't enough precision, they can not zoom far enough. This is confusing, so we don't use this layer
continue;
}
if (props.name === undefined) {
console.warn("Editor layer index: name not defined on ", props)
continue
}
const leafletLayer: () => TileLayer = () => AvailableBaseLayers.CreateBackgroundLayer(
props.id,
props.name,
props.url,
props.name,
props.license_url,
props.max_zoom,
props.type.toLowerCase() === "wms",
props.type.toLowerCase() === "wmts"
)
// Note: if layer.geometry is null, there is global coverage for this layer
layers.push({
id: props.id,
max_zoom: props.max_zoom ?? 25,
min_zoom: props.min_zoom ?? 1,
name: props.name,
layer: leafletLayer,
feature: layer,
isBest: props.best ?? false,
category: props.category
});
}
return layers;
}
private static LoadProviderIndex(): BaseLayer[] {
// @ts-ignore
X; // Import X to make sure the namespace is not optimized away
function l(id: string, name: string): BaseLayer {
try {
const layer: any = () => L.tileLayer.provider(id, undefined);
return {
feature: null,
id: id,
name: name,
layer: layer,
min_zoom: layer.minzoom,
max_zoom: layer.maxzoom,
category: "osmbasedmap",
isBest: false
}
} catch (e) {
console.error("Could not find provided layer", name, e);
return null;
}
}
const layers = [
l("CyclOSM", "CyclOSM - A bicycle oriented map"),
l("Stamen.TonerLite", "Toner Lite (by Stamen)"),
l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"),
l("Stamen.Watercolor", "Watercolor (by Stamen)"),
l("Stadia.OSMBright", "Osm Bright (by Stadia)"),
l("CartoDB.Positron", "Positron (by CartoDB)"),
l("CartoDB.PositronNoLabels", "Positron - no labels (by CartoDB)"),
l("CartoDB.Voyager", "Voyager (by CartoDB)"),
l("CartoDB.VoyagerNoLabels", "Voyager - no labels (by CartoDB)"),
];
return Utils.NoNull(layers);
static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
return AvailableBaseLayers.implementation.SelectBestLayerAccordingTo(location, preferedCategory);
}
/**
* Converts a layer from the editor-layer-index into a tilelayer usable by leaflet
*/
private static CreateBackgroundLayer(id: string, name: string, url: string, attribution: string, attributionUrl: string,
maxZoom: number, isWms: boolean, isWMTS?: boolean): TileLayer {
url = url.replace("{zoom}", "{z}")
.replace("&BBOX={bbox}", "")
.replace("&bbox={bbox}", "");
const subdomainsMatch = url.match(/{switch:[^}]*}/)
let domains: string[] = [];
if (subdomainsMatch !== null) {
let domainsStr = subdomainsMatch[0].substr("{switch:".length);
domainsStr = domainsStr.substr(0, domainsStr.length - 1);
domains = domainsStr.split(",");
url = url.replace(/{switch:[^}]*}/, "{s}")
}
if (isWms) {
url = url.replace("&SRS={proj}", "");
url = url.replace("&srs={proj}", "");
const paramaters = ["format", "layers", "version", "service", "request", "styles", "transparent", "version"];
const urlObj = new URL(url);
const isUpper = urlObj.searchParams["LAYERS"] !== null;
const options = {
maxZoom: maxZoom ?? 19,
attribution: attribution + " | ",
subdomains: domains,
uppercase: isUpper,
transparent: false
};
for (const paramater of paramaters) {
let p = paramater;
if (isUpper) {
p = paramater.toUpperCase();
}
options[paramater] = urlObj.searchParams.get(p);
}
if (options.transparent === null) {
options.transparent = false;
}
return L.tileLayer.wms(urlObj.protocol + "//" + urlObj.host + urlObj.pathname, options);
}
if (attributionUrl) {
attribution = `<a href='${attributionUrl}' target='_blank'>${attribution}</a>`;
}
return L.tileLayer(url,
{
attribution: attribution,
maxZoom: maxZoom,
minZoom: 1,
// @ts-ignore
wmts: isWMTS ?? false,
subdomains: domains
});
public static implement(backend: AvailableBaseLayersObj){
AvailableBaseLayers.layerOverview = backend.layerOverview
AvailableBaseLayers.osmCarto = backend.osmCarto
AvailableBaseLayers.implementation = backend
}
}

View file

@ -0,0 +1,292 @@
import BaseLayer from "../../Models/BaseLayer";
import {UIEventSource} from "../UIEventSource";
import Loc from "../../Models/Loc";
import {GeoOperations} from "../GeoOperations";
import * as editorlayerindex from "../../assets/editor-layer-index.json";
import {TileLayer} from "leaflet";
import * as X from "leaflet-providers";
import * as L from "leaflet";
import {Utils} from "../../Utils";
import {AvailableBaseLayersObj} from "./AvailableBaseLayers";
export default class AvailableBaseLayersImplementation implements AvailableBaseLayersObj{
public readonly osmCarto: BaseLayer =
{
id: "osm",
name: "OpenStreetMap",
layer: () => AvailableBaseLayersImplementation.CreateBackgroundLayer("osm", "OpenStreetMap",
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright",
19,
false, false),
feature: null,
max_zoom: 19,
min_zoom: 0,
isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context)
category: "osmbasedmap"
}
public layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex());
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
const source = location.map(
(currentLocation) => {
if (currentLocation === undefined) {
return this.layerOverview;
}
const currentLayers = source?.data; // A bit unorthodox - I know
const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
if (currentLayers === undefined) {
return newLayers;
}
if (newLayers.length !== currentLayers.length) {
return newLayers;
}
for (let i = 0; i < newLayers.length; i++) {
if (newLayers[i].name !== currentLayers[i].name) {
return newLayers;
}
}
return currentLayers;
});
return source;
}
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
return this.AvailableLayersAt(location).map(available => {
// First float all 'best layers' to the top
available.sort((a, b) => {
if (a.isBest && b.isBest) {
return 0;
}
if (!a.isBest) {
return 1
}
return -1;
}
)
if (preferedCategory.data === undefined) {
return available[0]
}
let prefered: string []
if (typeof preferedCategory.data === "string") {
prefered = [preferedCategory.data]
} else {
prefered = preferedCategory.data;
}
prefered.reverse();
for (const category of prefered) {
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
available.sort((a, b) => {
if (a.category === category && b.category === category) {
return 0;
}
if (a.category !== category) {
return 1
}
return -1;
}
)
}
return available[0]
})
}
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [this.osmCarto]
const globalLayers = [];
for (const layerOverviewItem of this.layerOverview) {
const layer = layerOverviewItem;
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
globalLayers.push(layer);
continue;
}
if (lon === undefined || lat === undefined) {
continue;
}
if (GeoOperations.inside([lon, lat], layer.feature)) {
availableLayers.push(layer);
}
}
return availableLayers.concat(globalLayers);
}
private static LoadRasterIndex(): BaseLayer[] {
const layers: BaseLayer[] = []
// @ts-ignore
const features = editorlayerindex.features;
for (const i in features) {
const layer = features[i];
const props = layer.properties;
if (props.id === "Bing") {
// Doesnt work
continue;
}
if (props.id === "MAPNIK") {
// Already added by default
continue;
}
if (props.overlay) {
continue;
}
if (props.url.toLowerCase().indexOf("apikey") > 0) {
continue;
}
if (props.max_zoom < 19) {
// We want users to zoom to level 19 when adding a point
// If they are on a layer which hasn't enough precision, they can not zoom far enough. This is confusing, so we don't use this layer
continue;
}
if (props.name === undefined) {
console.warn("Editor layer index: name not defined on ", props)
continue
}
const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer(
props.id,
props.name,
props.url,
props.name,
props.license_url,
props.max_zoom,
props.type.toLowerCase() === "wms",
props.type.toLowerCase() === "wmts"
)
// Note: if layer.geometry is null, there is global coverage for this layer
layers.push({
id: props.id,
max_zoom: props.max_zoom ?? 25,
min_zoom: props.min_zoom ?? 1,
name: props.name,
layer: leafletLayer,
feature: layer,
isBest: props.best ?? false,
category: props.category
});
}
return layers;
}
private static LoadProviderIndex(): BaseLayer[] {
// @ts-ignore
X; // Import X to make sure the namespace is not optimized away
function l(id: string, name: string): BaseLayer {
try {
const layer: any = () => L.tileLayer.provider(id, undefined);
return {
feature: null,
id: id,
name: name,
layer: layer,
min_zoom: layer.minzoom,
max_zoom: layer.maxzoom,
category: "osmbasedmap",
isBest: false
}
} catch (e) {
console.error("Could not find provided layer", name, e);
return null;
}
}
const layers = [
l("CyclOSM", "CyclOSM - A bicycle oriented map"),
l("Stamen.TonerLite", "Toner Lite (by Stamen)"),
l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"),
l("Stamen.Watercolor", "Watercolor (by Stamen)"),
l("Stadia.OSMBright", "Osm Bright (by Stadia)"),
l("CartoDB.Positron", "Positron (by CartoDB)"),
l("CartoDB.PositronNoLabels", "Positron - no labels (by CartoDB)"),
l("CartoDB.Voyager", "Voyager (by CartoDB)"),
l("CartoDB.VoyagerNoLabels", "Voyager - no labels (by CartoDB)"),
];
return Utils.NoNull(layers);
}
/**
* Converts a layer from the editor-layer-index into a tilelayer usable by leaflet
*/
private static CreateBackgroundLayer(id: string, name: string, url: string, attribution: string, attributionUrl: string,
maxZoom: number, isWms: boolean, isWMTS?: boolean): TileLayer {
url = url.replace("{zoom}", "{z}")
.replace("&BBOX={bbox}", "")
.replace("&bbox={bbox}", "");
const subdomainsMatch = url.match(/{switch:[^}]*}/)
let domains: string[] = [];
if (subdomainsMatch !== null) {
let domainsStr = subdomainsMatch[0].substr("{switch:".length);
domainsStr = domainsStr.substr(0, domainsStr.length - 1);
domains = domainsStr.split(",");
url = url.replace(/{switch:[^}]*}/, "{s}")
}
if (isWms) {
url = url.replace("&SRS={proj}", "");
url = url.replace("&srs={proj}", "");
const paramaters = ["format", "layers", "version", "service", "request", "styles", "transparent", "version"];
const urlObj = new URL(url);
const isUpper = urlObj.searchParams["LAYERS"] !== null;
const options = {
maxZoom: maxZoom ?? 19,
attribution: attribution + " | ",
subdomains: domains,
uppercase: isUpper,
transparent: false
};
for (const paramater of paramaters) {
let p = paramater;
if (isUpper) {
p = paramater.toUpperCase();
}
options[paramater] = urlObj.searchParams.get(p);
}
if (options.transparent === null) {
options.transparent = false;
}
return L.tileLayer.wms(urlObj.protocol + "//" + urlObj.host + urlObj.pathname, options);
}
if (attributionUrl) {
attribution = `<a href='${attributionUrl}' target='_blank'>${attribution}</a>`;
}
return L.tileLayer(url,
{
attribution: attribution,
maxZoom: maxZoom,
minZoom: 1,
// @ts-ignore
wmts: isWMTS ?? false,
subdomains: domains
});
}
}

View file

@ -179,7 +179,7 @@ export default class OverpassFeatureSource implements FeatureSource {
self.retries.setData(0);
try {
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date));
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined));
self.features.setData(data.features.map(f => ({feature: f, freshness: date})));
return [bounds, date, layersToDownload];
} catch (e) {

View file

@ -80,5 +80,6 @@ export default class StrayClickHandler {
}
}

View file

@ -121,50 +121,7 @@ export default class FeaturePipelineState extends MapState {
})
}
public setupClickDialogOnMap(filterViewIsOpened: UIEventSource<boolean>, leafletMap: UIEventSource<any>) {
const self = this
function setup(){
let presetCount = 0;
for (const layer of self.layoutToUse.layers) {
for (const preset of layer.presets) {
presetCount++;
}
}
if (presetCount == 0) {
return;
}
const newPointDialogIsShown = new UIEventSource<boolean>(false);
const addNewPoint = new ScrollableFullScreen(
() => Translations.t.general.add.title.Clone(),
() => new SimpleAddUI(newPointDialogIsShown, filterViewIsOpened, self),
"new",
newPointDialogIsShown
);
addNewPoint.isShown.addCallback((isShown) => {
if (!isShown) {
self.LastClickLocation.setData(undefined);
}
});
new StrayClickHandler(
self.LastClickLocation,
self.selectedElement,
self.filteredLayers,
leafletMap,
addNewPoint
);
}
this.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => {
if (addNewAllowed) {
setup()
return true;
}
})
}

View file

@ -7,9 +7,6 @@ import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter";
import Attribution from "../../UI/BigComponents/Attribution";
import Minimap, {MinimapObj} from "../../UI/Base/Minimap";
import {Tiles} from "../../Models/TileRange";
import * as L from "leaflet";
import Img from "../../UI/Base/Img";
import Svg from "../../Svg";
import BaseUIElement from "../../UI/BaseUIElement";
import FilteredLayer from "../../Models/FilteredLayer";
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
@ -26,7 +23,7 @@ export default class MapState extends UserRelatedState {
/**
The leaflet instance of the big basemap
*/
public leafletMap = new UIEventSource<L.Map>(undefined, "leafletmap");
public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap");
/**
* A list of currently available background layers
*/
@ -67,7 +64,6 @@ export default class MapState extends UserRelatedState {
*/
public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]
constructor(layoutToUse: LayoutConfig) {
super(layoutToUse);
@ -120,57 +116,19 @@ export default class MapState extends UserRelatedState {
lastClickLocation: this.LastClickLocation
})
this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({
config: c,
isDisplayed: QueryParameters.GetQueryParameter("overlay-" + c.id, "" + c.defaultState, "Wether or not the overlay " + c.id + " is shown").map(str => str === "true", [], b => "" + b)
}))
this.filteredLayers = this.InitializeFilteredLayers()
this.lockBounds()
this.AddAllOverlaysToMap(this.leafletMap)
this.addHomeMarker()
}
private addHomeMarker() {
const leafletMap = this.leafletMap
const osmConnection = this.osmConnection
function addHomeMarker() {
const userDetails = osmConnection.userDetails.data;
if (userDetails === undefined) {
return false;
}
const home = userDetails.home;
if (home === undefined) {
return userDetails.loggedIn; // If logged in, the home is not set and we unregister. If not logged in, we stay registered if a login still comes
}
const leaflet = leafletMap.data;
if (leaflet === undefined) {
return false;
}
const color = getComputedStyle(document.body).getPropertyValue(
"--subtle-detail-color"
);
const icon = L.icon({
iconUrl: Img.AsData(
Svg.home_white_bg.replace(/#ffffff/g, color)
),
iconSize: [30, 30],
iconAnchor: [15, 15],
});
const marker = L.marker([home.lat, home.lon], {icon: icon});
marker.addTo(leaflet);
return true;
}
osmConnection.userDetails.addCallbackAndRunD(_ => addHomeMarker());
leafletMap.addCallbackAndRunD(_ => addHomeMarker())
}
private lockBounds() {
const layout = this.layoutToUse;
@ -198,6 +156,7 @@ export default class MapState extends UserRelatedState {
})
}
}
private InitializeFilteredLayers() {
// Initialize the filtered layers state
@ -252,8 +211,8 @@ export default class MapState extends UserRelatedState {
return new UIEventSource<FilteredLayer[]>(flayers);
}
public AddAllOverlaysToMap(leafletMap: UIEventSource<any>){
const initialized =new Set()
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)

View file

@ -9,6 +9,8 @@ import {Utils} from "../../Utils";
import Locale from "../../UI/i18n/Locale";
import ElementsState from "./ElementsState";
import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater";
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource";
import FeatureSource from "../FeatureSource/FeatureSource";
/**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
@ -34,6 +36,10 @@ export default class UserRelatedState extends ElementsState {
* WHich other themes the user previously visited
*/
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
/**
* A feature source containing the current home location of the user
*/
public homeLocation: FeatureSource
constructor(layoutToUse: LayoutConfig) {
super(layoutToUse);
@ -104,4 +110,37 @@ export default class UserRelatedState extends ElementsState {
.ping();
}
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 [{
"type": "Feature",
"properties": {
"user:home": "yes",
"_lon": homeLonLat[0],
"_lat": homeLonLat[1]
},
"geometry": {
"type": "Point",
"coordinates": homeLonLat
}
}]
})
this.homeLocation = new StaticFeatureSource(feature, false)
}
}

View file

@ -136,7 +136,7 @@ export class UIEventSource<T> {
if (oldList === list) {
return;
}
if (oldList.length !== list.length) {
if (oldList === undefined || oldList.length !== list.length) {
stable.setData(list);
return;
}

View file

@ -117,7 +117,7 @@ export interface LayoutConfigJson {
* These tiles are using a ceratin zoom level, that can be controlled here
* Default: overpassMaxZoom + 1
*/
osmApiTileSize: number
osmApiTileSize?: number
/**
* A tagrendering depicts how to show some tags or how to show a question for it.

View file

@ -17,6 +17,12 @@ import {VariableUiElement} from "./Base/VariableUIElement";
import LeftControls from "./BigComponents/LeftControls";
import RightControls from "./BigComponents/RightControls";
import CenterMessageBox from "./CenterMessageBox";
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
import AllKnownLayers from "../Customizations/AllKnownLayers";
import ScrollableFullScreen from "./Base/ScrollableFullScreen";
import Translations from "./i18n/Translations";
import SimpleAddUI from "./BigComponents/SimpleAddUI";
import StrayClickHandler from "../Logic/Actors/StrayClickHandler";
export class DefaultGuiState {
public readonly welcomeMessageIsOpened;
@ -85,9 +91,9 @@ export default class DefaultGUI {
state.mainMapObject.SetClass("w-full h-full")
.AttachTo("leafletDiv")
state.setupClickDialogOnMap(
this.setupClickDialogOnMap(
guiState.filterViewIsOpened,
state.leafletMap
state
)
this.InitWelcomeMessage();
@ -125,6 +131,14 @@ export default class DefaultGUI {
document
.getElementById("centermessage")
.classList.add("pointer-events-none");
new ShowDataLayer({
leafletMap: state.leafletMap,
layerToShow: AllKnownLayers.sharedLayers.get("home_location"),
features: state.homeLocation,
enablePopups: false,
})
}
@ -158,4 +172,48 @@ export default class DefaultGUI {
}
public setupClickDialogOnMap(filterViewIsOpened: UIEventSource<boolean>, state: FeaturePipelineState) {
function setup(){
let presetCount = 0;
for (const layer of state.layoutToUse.layers) {
for (const preset of layer.presets) {
presetCount++;
}
}
if (presetCount == 0) {
return;
}
const newPointDialogIsShown = new UIEventSource<boolean>(false);
const addNewPoint = new ScrollableFullScreen(
() => Translations.t.general.add.title.Clone(),
() => new SimpleAddUI(newPointDialogIsShown, filterViewIsOpened, state),
"new",
newPointDialogIsShown
);
addNewPoint.isShown.addCallback((isShown) => {
if (!isShown) {
state.LastClickLocation.setData(undefined);
}
});
new StrayClickHandler(
state.LastClickLocation,
state.selectedElement,
state.filteredLayers,
state.leafletMap,
addNewPoint
);
}
state.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => {
if (addNewAllowed) {
setup()
return true;
}
})
}
}

View file

@ -18,6 +18,7 @@ import {Unit} from "../../Models/Unit";
import {FixedInputElement} from "./FixedInputElement";
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox";
import Wikidata from "../../Logic/Web/Wikidata";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
interface TextFieldDef {
name: string,
@ -35,8 +36,6 @@ interface TextFieldDef {
export default class ValidatedTextField {
public static bestLayerAt: (location: UIEventSource<Loc>, preferences: UIEventSource<string[]>) => any
public static tpList: TextFieldDef[] = [
ValidatedTextField.tp(
"string",
@ -93,7 +92,7 @@ export default class ValidatedTextField {
})
if (args[1]) {
// We have a prefered map!
options.mapBackgroundLayer = ValidatedTextField.bestLayerAt(
options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(
location, new UIEventSource<string[]>(args[1].split(","))
)
}
@ -137,7 +136,7 @@ export default class ValidatedTextField {
})
if (args[1]) {
// We have a prefered map!
options.mapBackgroundLayer = ValidatedTextField.bestLayerAt(
options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(
location, new UIEventSource<string[]>(args[1].split(","))
)
}

View file

@ -18,6 +18,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import MoveConfig from "../../Models/ThemeConfig/MoveConfig";
import {ElementStorage} from "../../Logic/ElementStorage";
import ValidatedTextField from "../Input/ValidatedTextField";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
interface MoveReason {
text: Translation | string,
@ -138,7 +139,7 @@ export default class MoveWizard extends Toggle {
const locationInput = new LocationInput({
minZoom: reason.minZoom,
centerLocation: loc,
mapBackground: ValidatedTextField.bestLayerAt(loc, new UIEventSource(background))
mapBackground:AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background))
})
if (reason.lockBounds) {

View file

@ -3,43 +3,17 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import * as L from "leaflet";
export default class ShowOverlayLayer {
public static implementation: (config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown?: UIEventSource<boolean>) => void;
constructor(config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined) {
leafletMap.map(leaflet => {
if(leaflet === undefined){
return;
}
const tileLayer = L.tileLayer(config.source,
{
attribution: "",
maxZoom: config.maxzoom,
minZoom: config.minzoom,
// @ts-ignore
wmts: false,
});
if(isShown === undefined){
tileLayer.addTo(leaflet)
}
isShown?.addCallbackAndRunD(isShown => {
if(isShown){
tileLayer.addTo(leaflet)
}else{
leaflet.removeLayer(tileLayer)
}
})
} )
if(ShowOverlayLayer.implementation === undefined){
throw "Call ShowOverlayLayerImplemenation.initialize() first before using this"
}
ShowOverlayLayer.implementation(config, leafletMap, isShown)
}
}

View file

@ -0,0 +1,45 @@
import * as L from "leaflet";
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import ShowOverlayLayer from "./ShowOverlayLayer";
export default class ShowOverlayLayerImplementation {
public static Implement(){
ShowOverlayLayer.implementation = ShowOverlayLayerImplementation.AddToMap
}
public static AddToMap(config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined){
leafletMap.map(leaflet => {
if (leaflet === undefined) {
return;
}
const tileLayer = L.tileLayer(config.source,
{
attribution: "",
maxZoom: config.maxzoom,
minZoom: config.minzoom,
// @ts-ignore
wmts: false,
});
if (isShown === undefined) {
tileLayer.addTo(leaflet)
}
isShown?.addCallbackAndRunD(isShown => {
if (isShown) {
tileLayer.addTo(leaflet)
} else {
leaflet.removeLayer(tileLayer)
}
})
})
}
}

View file

@ -0,0 +1,17 @@
{
"id": "home_location",
"description": "Meta layer showing the home location of the user",
"minzoom": 0,
"source": {
"osmTags": "user:home=yes"
},
"icon": {
"render": "circle:white;./assets/svg/home.svg"
},
"iconSize": {
"render": "20,20,center"
},
"color": {
"render": "#00f"
}
}

View file

@ -12,11 +12,16 @@ import DetermineLayout from "./Logic/DetermineLayout";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
import DefaultGUI, {DefaultGuiState} from "./UI/DefaultGUI";
import State from "./State";
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation";
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
MinimapImplementation.initialize()
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts
ValidatedTextField.bestLayerAt = (location, layerPref) => AvailableBaseLayers.SelectBestLayerAccordingTo(location, layerPref)
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");
ShowOverlayLayerImplementation.Implement();
// Miscelleanous
Utils.DisableLongPresses()
// --------------------- Special actions based on the parameters -----------------