More work on cyclestreet layout, add loading of layers depending on zoom level
This commit is contained in:
parent
3576a4b1e1
commit
9a5b35b9f3
17 changed files with 109 additions and 59 deletions
|
@ -57,6 +57,7 @@ export class AllKnownLayouts {
|
|||
continue;
|
||||
}
|
||||
this.allLayers[layer.id] = layer;
|
||||
this.allLayers[layer.id.toLowerCase()] = layer;
|
||||
all.layers.push(layer);
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +65,7 @@ export class AllKnownLayouts {
|
|||
const allSets: Map<string, Layout> = new Map();
|
||||
for (const layout of this.layoutsList) {
|
||||
allSets[layout.name] = layout;
|
||||
allSets[layout.name.toLowerCase()] = layout;
|
||||
}
|
||||
allSets[all.name] = all;
|
||||
return allSets;
|
||||
|
|
|
@ -45,6 +45,7 @@ export interface LayerConfigJson {
|
|||
width?: TagRenderingConfigJson;
|
||||
overpassTags: string | { k: string, v: string }[];
|
||||
wayHandling?: number,
|
||||
widenFactor?: number,
|
||||
presets: {
|
||||
tags: string,
|
||||
title: string | any,
|
||||
|
|
|
@ -106,6 +106,7 @@ export class LayerDefinition {
|
|||
elementsToShow?: TagDependantUIElementConstructor[],
|
||||
maxAllowedOverlapPercentage?: number,
|
||||
wayHandling?: number,
|
||||
widenFactor?: number,
|
||||
style?: (tags: any) => {
|
||||
color: string,
|
||||
icon: any
|
||||
|
|
|
@ -13,7 +13,7 @@ export default class Cyclofix extends Layout {
|
|||
constructor() {
|
||||
super(
|
||||
"cyclofix",
|
||||
["en", "nl", "fr"],
|
||||
["en", "nl", "fr","gl"],
|
||||
Translations.t.cyclofix.title,
|
||||
[new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings(), new BikeOtherShops(), new BikeCafes()],
|
||||
16,
|
||||
|
|
|
@ -183,7 +183,6 @@ export class InitUiElements {
|
|||
const flayers: FilteredLayer[] = []
|
||||
const presets: Preset[] = [];
|
||||
|
||||
let minZoom = 0;
|
||||
const state = State.state;
|
||||
for (const layer of state.layoutToUse.data.layers) {
|
||||
|
||||
|
@ -197,9 +196,6 @@ export class InitUiElements {
|
|||
)
|
||||
};
|
||||
|
||||
minZoom = Math.max(minZoom, layer.minzoom);
|
||||
|
||||
|
||||
for (const preset of layer.presets ?? []) {
|
||||
|
||||
if (preset.icon === undefined) {
|
||||
|
|
|
@ -29,7 +29,7 @@ export class FilteredLayer {
|
|||
|
||||
/** The featurecollection from overpass
|
||||
*/
|
||||
private _dataFromOverpass;
|
||||
private _dataFromOverpass : any[];
|
||||
private _wayHandling: number;
|
||||
/** List of new elements, geojson features
|
||||
*/
|
||||
|
@ -146,7 +146,7 @@ export class FilteredLayer {
|
|||
public AddNewElement(element) {
|
||||
this._newElements.push(element);
|
||||
console.log("Element added");
|
||||
this.RenderLayer(this._dataFromOverpass); // Update the layer
|
||||
this.RenderLayer({features:this._dataFromOverpass}); // Update the layer
|
||||
|
||||
}
|
||||
|
||||
|
@ -154,23 +154,39 @@ export class FilteredLayer {
|
|||
let self = this;
|
||||
|
||||
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);
|
||||
}
|
||||
this._dataFromOverpass = data;
|
||||
|
||||
const oldData = this._dataFromOverpass ?? [];
|
||||
|
||||
// We keep track of all the ids that are freshly loaded in order to avoid adding duplicates
|
||||
const idsFromOverpass: Set<number> = new Set<number>();
|
||||
// A list of all the features to show
|
||||
const fusedFeatures = [];
|
||||
const idsFromOverpass = [];
|
||||
// First, we add all the fresh data:
|
||||
for (const feature of data.features) {
|
||||
idsFromOverpass.push(feature.properties.id);
|
||||
idsFromOverpass.add(feature.properties.id);
|
||||
fusedFeatures.push(feature);
|
||||
}
|
||||
// Now we add all the stale data
|
||||
for (const feature of oldData) {
|
||||
if (idsFromOverpass.has(feature.properties.id)) {
|
||||
continue; // Feature already loaded and a fresher version is available
|
||||
}
|
||||
idsFromOverpass.add(feature.properties.id);
|
||||
fusedFeatures.push(feature);
|
||||
}
|
||||
|
||||
for (const feature of this._newElements) {
|
||||
if (idsFromOverpass.indexOf(feature.properties.id) < 0) {
|
||||
if (idsFromOverpass.has(feature.properties.id)) {
|
||||
// This element is not yet uploaded or not yet visible in overpass
|
||||
// We include it in the layer
|
||||
fusedFeatures.push(feature);
|
||||
}
|
||||
}
|
||||
|
||||
this._dataFromOverpass = fusedFeatures;
|
||||
|
||||
// We use a new, fused dataset
|
||||
data = {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {ImgurImage} from "../UI/Image/ImgurImage";
|
|||
import {State} from "../State";
|
||||
import {ImagesInCategory, Wikidata, Wikimedia} from "./Web/Wikimedia";
|
||||
import {UIEventSource} from "./UIEventSource";
|
||||
import {Tag} from "./TagsFilter";
|
||||
|
||||
/**
|
||||
* There are multiple way to fetch images for an object
|
||||
|
@ -121,7 +122,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
|
|||
return;
|
||||
}
|
||||
console.log("Deleting image...", key, " --> ", url);
|
||||
State.state.changes.addChange(this._tags.data.id, key, "");
|
||||
State.state.changes.addTag(this._tags.data.id, new Tag(key, ""));
|
||||
this._deletedImages.data.push(url);
|
||||
this._deletedImages.ping();
|
||||
}
|
||||
|
|
|
@ -8,13 +8,17 @@ import {State} from "../State";
|
|||
|
||||
export class LayerUpdater {
|
||||
|
||||
public readonly sufficentlyZoomed: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
public readonly sufficentlyZoomed: UIEventSource<boolean>;
|
||||
public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
public readonly retries: UIEventSource<number> = new UIEventSource<number>(0);
|
||||
/**
|
||||
* The previous bounds for which the query has been run
|
||||
* The previous bounds for which the query has been run at the given zoom level
|
||||
*
|
||||
* Note that some layers only activate on a certain zoom level.
|
||||
* If the map location changes, we check for each layer if it is loaded:
|
||||
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
|
||||
*/
|
||||
private previousBounds: Bounds;
|
||||
private previousBounds: Map<number, Bounds[]> = new Map<number, Bounds[]>();
|
||||
|
||||
/**
|
||||
* The most important layer should go first, as that one gets first pick for the questions
|
||||
|
@ -25,6 +29,13 @@ export class LayerUpdater {
|
|||
constructor(state: State) {
|
||||
|
||||
const self = this;
|
||||
|
||||
let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom));
|
||||
this.sufficentlyZoomed = State.state.locationControl.map(location => location.zoom >= minzoom);
|
||||
for (let i = 0; i < 25; i++) {
|
||||
// This update removes all data on all layers -> erase the map on lower levels too
|
||||
this.previousBounds.set(i, []);
|
||||
}
|
||||
state.locationControl.addCallback(() => {
|
||||
self.update(state)
|
||||
});
|
||||
|
@ -40,13 +51,30 @@ export class LayerUpdater {
|
|||
state = state ?? State.state;
|
||||
for (const layer of state.layoutToUse.data.layers) {
|
||||
if (state.locationControl.data.zoom < layer.minzoom) {
|
||||
console.log("Not loading layer ", layer.id, " as it needs at least ",layer.minzoom, "zoom")
|
||||
console.log("Not loading layer ", layer.id, " as it needs at least ", layer.minzoom, "zoom")
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if data for this layer has already been loaded
|
||||
let previouslyLoaded = false;
|
||||
for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) {
|
||||
const previousLoadedBounds = this.previousBounds.get(z);
|
||||
if (previousLoadedBounds == undefined) {
|
||||
continue;
|
||||
}
|
||||
for (const previousLoadedBound of previousLoadedBounds) {
|
||||
previouslyLoaded = previouslyLoaded || this.IsInBounds(state, previousLoadedBound);
|
||||
if(previouslyLoaded){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (previouslyLoaded) {
|
||||
continue;
|
||||
}
|
||||
filters.push(layer.overpassFilter);
|
||||
}
|
||||
if (filters.length === 0) {
|
||||
console.log("No layers loaded at all")
|
||||
return undefined;
|
||||
}
|
||||
return new Or(filters);
|
||||
|
@ -66,8 +94,8 @@ export class LayerUpdater {
|
|||
}
|
||||
return;
|
||||
}
|
||||
// We use window.setTimeout to give JS some time to update everything and make the interface not too laggy
|
||||
window.setTimeout(() => {
|
||||
|
||||
const layer = layers[0];
|
||||
const rest = layers.slice(1, layers.length);
|
||||
geojson = layer.SetApplicableData(geojson);
|
||||
|
@ -94,15 +122,7 @@ export class LayerUpdater {
|
|||
|
||||
|
||||
private update(state: State): void {
|
||||
if (this.IsInBounds(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const filter = this.GetFilter(state);
|
||||
|
||||
|
||||
this.sufficentlyZoomed.setData(filter !== undefined);
|
||||
if (filter === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -117,16 +137,19 @@ export class LayerUpdater {
|
|||
const diff = state.layoutToUse.data.widenFactor;
|
||||
|
||||
const n = Math.min(90, bounds.getNorth() + diff);
|
||||
const e = Math.min( 180,bounds.getEast() + diff);
|
||||
const e = Math.min(180, bounds.getEast() + diff);
|
||||
const s = Math.max(-90, bounds.getSouth() - diff);
|
||||
const w = Math.max(-180, bounds.getWest() - diff);
|
||||
const queryBounds = {north: n, east: e, south: s, west: w};
|
||||
|
||||
this.previousBounds = {north: n, east: e, south: s, west: w};
|
||||
const z = state.locationControl.data.zoom;
|
||||
|
||||
this.previousBounds.get(z).push(queryBounds);
|
||||
|
||||
this.runningQuery.setData(true);
|
||||
const self = this;
|
||||
const overpass = new Overpass(filter);
|
||||
overpass.queryGeoJson(this.previousBounds,
|
||||
overpass.queryGeoJson(queryBounds,
|
||||
function (data) {
|
||||
self.handleData(data)
|
||||
},
|
||||
|
@ -138,7 +161,7 @@ export class LayerUpdater {
|
|||
}
|
||||
|
||||
|
||||
private IsInBounds(state: State): boolean {
|
||||
private IsInBounds(state: State, bounds: Bounds): boolean {
|
||||
|
||||
if (this.previousBounds === undefined) {
|
||||
return false;
|
||||
|
@ -146,18 +169,18 @@ export class LayerUpdater {
|
|||
|
||||
|
||||
const b = state.bm.map.getBounds();
|
||||
if (b.getSouth() < this.previousBounds.south) {
|
||||
if (b.getSouth() < bounds.south) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (b.getNorth() > this.previousBounds.north) {
|
||||
if (b.getNorth() > bounds.north) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (b.getEast() > this.previousBounds.east) {
|
||||
if (b.getEast() > bounds.east) {
|
||||
return false;
|
||||
}
|
||||
if (b.getWest() < this.previousBounds.west) {
|
||||
if (b.getWest() < bounds.west) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,12 @@ export class Basemap {
|
|||
});
|
||||
|
||||
|
||||
// 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.Location = location;
|
||||
|
|
|
@ -130,9 +130,7 @@ export class OsmConnection {
|
|||
}, function (err, details) {
|
||||
if(err != null){
|
||||
console.log(err);
|
||||
self.auth.logout();
|
||||
self.userDetails.data.loggedIn = false;
|
||||
self.userDetails.ping();
|
||||
return;
|
||||
}
|
||||
|
||||
if (details == null) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
|||
import {UserDetails} from "./OsmConnection";
|
||||
import {SlideShow} from "../../UI/SlideShow";
|
||||
import {State} from "../../State";
|
||||
import {Tag} from "../TagsFilter";
|
||||
|
||||
export class OsmImageUploadHandler {
|
||||
private _tags: UIEventSource<any>;
|
||||
|
@ -51,7 +52,7 @@ export class OsmImageUploadHandler {
|
|||
key = "image:" + freeIndex;
|
||||
}
|
||||
console.log("Adding image:" + key, url);
|
||||
changes.addChange(tags.id, key, url);
|
||||
changes.addTag(tags.id, new Tag(key, url));
|
||||
self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view
|
||||
},
|
||||
allDone: () => {
|
||||
|
|
|
@ -94,7 +94,7 @@ export class PersonalLayersPanel extends UIElement {
|
|||
]),
|
||||
controls[layer.id] ?? (favs.indexOf(layer.id) >= 0)
|
||||
);
|
||||
cb.clss = "custom-layer-checkbox"
|
||||
cb.SetClass("custom-layer-checkbox");
|
||||
controls[layer.id] = cb.isEnabled;
|
||||
|
||||
cb.isEnabled.addCallback((isEnabled) => {
|
||||
|
|
8
State.ts
8
State.ts
|
@ -24,7 +24,7 @@ export class State {
|
|||
// The singleton of the global state
|
||||
public static state: State;
|
||||
|
||||
public static vNumber = "0.0.7b Less changesets";
|
||||
public static vNumber = "0.0.7c mutlizoom";
|
||||
|
||||
// The user journey states thresholds when a new feature gets unlocked
|
||||
public static userJourney = {
|
||||
|
@ -133,9 +133,9 @@ export class State {
|
|||
lat: Utils.asFloat(this.lat.data),
|
||||
lon: Utils.asFloat(this.lon.data),
|
||||
}).addCallback((latlonz) => {
|
||||
this.zoom.setData(latlonz.zoom.toString());
|
||||
this.lat.setData(latlonz.lat.toString().substr(0, 6));
|
||||
this.lon.setData(latlonz.lon.toString().substr(0, 6));
|
||||
this.zoom.setData(latlonz.zoom?.toString());
|
||||
this.lat.setData(latlonz.lat?.toString()?.substr(0, 6));
|
||||
this.lon.setData(latlonz.lon?.toString()?.substr(0, 6));
|
||||
});
|
||||
|
||||
this.layoutToUse.addCallback(layoutToUse => {
|
||||
|
|
|
@ -34,7 +34,7 @@ export class MoreScreen extends UIElement {
|
|||
|
||||
const currentLocation = State.state.locationControl.data;
|
||||
let linkText =
|
||||
`./${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
`./${layout.name.toLowerCase()}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||
linkText = `./index.html?layout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
|
@ -80,16 +80,16 @@ export class MoreScreen extends UIElement {
|
|||
|
||||
|
||||
for (const k in AllKnownLayouts.allSets) {
|
||||
|
||||
|
||||
const layout : Layout = AllKnownLayouts.allSets[k];
|
||||
if (k === PersonalLayout.NAME) {
|
||||
if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.customLayoutUnlock) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
els.push(this.createLinkButton(AllKnownLayouts.allSets[k]));
|
||||
if(layout.name !== k){
|
||||
continue; // This layout was added multiple time due to an uppercase
|
||||
}
|
||||
els.push(this.createLinkButton(layout));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -239,7 +239,12 @@ export default class Translations {
|
|||
fr: 'Est-ce que la pompe à un manomètre integré?',
|
||||
gl: 'Ten a bomba de ar un indicador de presión ou un manómetro?'
|
||||
}),
|
||||
yes: new T({en: 'There is a manometer', nl: 'Er is een luchtdrukmeter', fr: 'Il y a un manomètre'}),
|
||||
yes: new T({
|
||||
en: 'There is a manometer',
|
||||
nl: 'Er is een luchtdrukmeter',
|
||||
fr: 'Il y a un manomètre',
|
||||
gl: 'Hai manómetro'
|
||||
}),
|
||||
no: new T({
|
||||
en: 'There is no manometer',
|
||||
nl: 'Er is geen luchtdrukmeter',
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"render": "#0000ff"
|
||||
},
|
||||
"description": "Een fietsstraat is een straat waar gemotoriseerd verkeer een fietser niet mag inhalen.",
|
||||
"minzoom": "16",
|
||||
"minzoom": 9,
|
||||
"presets": [],
|
||||
"tagRenderings": [],
|
||||
"overpassTags": "cyclestreet=yes",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"render": "5"
|
||||
},
|
||||
"description": "Deze straat wordt binnenkort een fietsstraat",
|
||||
"minzoom": "16",
|
||||
"minzoom": "9",
|
||||
"wayHandling": 0,
|
||||
"presets": [],
|
||||
"tagRenderings": [{
|
||||
|
@ -121,7 +121,7 @@
|
|||
}
|
||||
],
|
||||
"type": "text",
|
||||
"question": "Is deze straat een fietsstraat?",
|
||||
"question": "Is deze straat een fietsstraat?"
|
||||
},
|
||||
{
|
||||
"key": "cyclestreet:start_date",
|
||||
|
@ -132,7 +132,7 @@
|
|||
}
|
||||
],
|
||||
"overpassTags": "highway~=residential|tertiary|unclassified",
|
||||
"minzoom": "13"
|
||||
"minzoom": "18"
|
||||
}
|
||||
],
|
||||
"language": "nl",
|
||||
|
@ -143,6 +143,6 @@
|
|||
"title": "Fietsstraten",
|
||||
"startLon": "3.2228",
|
||||
"icon": "./assets/themes/cyclestreets/F111.svg",
|
||||
"description": "Een fietsstraat is een straat waar automobilisten geen fietsers mogen inhalen en waar een maximumsnelheid van 30km/h geldt.<br/><br/>Op deze open kaart kan je alle gekende fietsstraten zien en kan je ontbrekende fietsstraten aanduiden.",
|
||||
"widenFactor": 0.03
|
||||
"description": "Een fietsstraat is een straat waar <b>automobilisten geen fietsers mogen inhalen</b> en waar een maximumsnelheid van <b>30km/u</b> geldt.<br/><br/>Op deze open kaart kan je alle gekende fietsstraten zien en kan je ontbrekende fietsstraten aanduiden. Om de kaart aan te passen, moet je je aanmelden met OpenStreetMap en helemaal inzoomen tot straatniveau.",
|
||||
"widenfactor": 0.05
|
||||
}
|
6
index.ts
6
index.ts
|
@ -47,13 +47,13 @@ let hash = window.location.hash;
|
|||
const path = window.location.pathname.split("/").slice(-1)[0];
|
||||
if (path !== "index.html") {
|
||||
defaultLayout = path.substr(0, path.length - 5);
|
||||
console.log("Using", defaultLayout)
|
||||
console.log("Using layout", defaultLayout)
|
||||
}
|
||||
|
||||
// Run over all questsets. If a part of the URL matches a searched-for part in the layout, it'll take that as the default
|
||||
for (const k in AllKnownLayouts.allSets) {
|
||||
const layout = AllKnownLayouts.allSets[k];
|
||||
const possibleParts = layout.locationContains ?? [];
|
||||
const possibleParts = (layout.locationContains ?? []);
|
||||
for (const locationMatch of possibleParts) {
|
||||
if (locationMatch === "") {
|
||||
continue
|
||||
|
@ -66,7 +66,7 @@ for (const k in AllKnownLayouts.allSets) {
|
|||
|
||||
defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
|
||||
|
||||
let layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"];
|
||||
let layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout.toLowerCase()] ?? AllKnownLayouts["all"];
|
||||
|
||||
|
||||
const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false");
|
||||
|
|
Loading…
Reference in a new issue