Add caching into local storage for a faster map experience
This commit is contained in:
parent
3a2d654ac3
commit
f33fe081d0
12 changed files with 128 additions and 41 deletions
|
@ -27,21 +27,16 @@ import Img from "./UI/Base/Img";
|
||||||
import UserDetails from "./Logic/Osm/OsmConnection";
|
import UserDetails from "./Logic/Osm/OsmConnection";
|
||||||
import Attribution from "./UI/BigComponents/Attribution";
|
import Attribution from "./UI/BigComponents/Attribution";
|
||||||
import MetaTagging from "./Logic/MetaTagging";
|
import MetaTagging from "./Logic/MetaTagging";
|
||||||
import FeatureSourceMerger from "./Logic/FeatureSource/FeatureSourceMerger";
|
|
||||||
import RememberingSource from "./Logic/FeatureSource/RememberingSource";
|
|
||||||
import FilteringFeatureSource from "./Logic/FeatureSource/FilteringFeatureSource";
|
|
||||||
import WayHandlingApplyingFeatureSource from "./Logic/FeatureSource/WayHandlingApplyingFeatureSource";
|
|
||||||
import NoOverlapSource from "./Logic/FeatureSource/NoOverlapSource";
|
|
||||||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
||||||
import LayerResetter from "./Logic/Actors/LayerResetter";
|
import LayerResetter from "./Logic/Actors/LayerResetter";
|
||||||
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
|
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
|
||||||
import LayerControlPanel from "./UI/BigComponents/LayerControlPanel";
|
import LayerControlPanel from "./UI/BigComponents/LayerControlPanel";
|
||||||
import FeatureSwitched from "./UI/Base/FeatureSwitched";
|
import FeatureSwitched from "./UI/Base/FeatureSwitched";
|
||||||
import FeatureDuplicatorPerLayer from "./Logic/FeatureSource/FeatureDuplicatorPerLayer";
|
|
||||||
import LayerConfig from "./Customizations/JSON/LayerConfig";
|
import LayerConfig from "./Customizations/JSON/LayerConfig";
|
||||||
import ShowDataLayer from "./UI/ShowDataLayer";
|
import ShowDataLayer from "./UI/ShowDataLayer";
|
||||||
import Hash from "./Logic/Web/Hash";
|
import Hash from "./Logic/Web/Hash";
|
||||||
import HistoryHandling from "./Logic/Actors/HistoryHandling";
|
import HistoryHandling from "./Logic/Actors/HistoryHandling";
|
||||||
|
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
|
||||||
|
|
||||||
export class InitUiElements {
|
export class InitUiElements {
|
||||||
|
|
||||||
|
@ -405,20 +400,9 @@ export class InitUiElements {
|
||||||
|
|
||||||
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
|
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
|
||||||
State.state.layerUpdater = updater;
|
State.state.layerUpdater = updater;
|
||||||
|
const source = new FeaturePipeline(flayers, updater);
|
||||||
|
|
||||||
|
|
||||||
const source =
|
|
||||||
new FilteringFeatureSource(
|
|
||||||
flayers,
|
|
||||||
State.state.locationControl,
|
|
||||||
new FeatureSourceMerger([
|
|
||||||
new RememberingSource(new WayHandlingApplyingFeatureSource(flayers,
|
|
||||||
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, updater))
|
|
||||||
)),
|
|
||||||
new FeatureDuplicatorPerLayer(flayers, State.state.changes)
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
source.features.addCallback((featuresFreshness: { feature: any, freshness: Date }[]) => {
|
source.features.addCallback((featuresFreshness: { feature: any, freshness: Date }[]) => {
|
||||||
let features = featuresFreshness.map(ff => ff.feature);
|
let features = featuresFreshness.map(ff => ff.feature);
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
|
|
|
@ -130,7 +130,7 @@ export default class UpdateFromOverpass implements FeatureSource{
|
||||||
const w = Math.max(-180, bounds.getWest() - diff);
|
const w = Math.max(-180, bounds.getWest() - diff);
|
||||||
const queryBounds = {north: n, east: e, south: s, west: w};
|
const queryBounds = {north: n, east: e, south: s, west: w};
|
||||||
|
|
||||||
const z = Math.floor(this._location.data.zoom);
|
const z = Math.floor(this._location.data.zoom ?? 0);
|
||||||
|
|
||||||
this.runningQuery.setData(true);
|
this.runningQuery.setData(true);
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
48
Logic/FeatureSource/FeaturePipeline.ts
Normal file
48
Logic/FeatureSource/FeaturePipeline.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import FilteringFeatureSource from "../FeatureSource/FilteringFeatureSource";
|
||||||
|
import State from "../../State";
|
||||||
|
import FeatureSourceMerger from "../FeatureSource/FeatureSourceMerger";
|
||||||
|
import RememberingSource from "../FeatureSource/RememberingSource";
|
||||||
|
import WayHandlingApplyingFeatureSource from "../FeatureSource/WayHandlingApplyingFeatureSource";
|
||||||
|
import NoOverlapSource from "../FeatureSource/NoOverlapSource";
|
||||||
|
import FeatureDuplicatorPerLayer from "../FeatureSource/FeatureDuplicatorPerLayer";
|
||||||
|
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import LocalStorageSaver from "./LocalStorageSaver";
|
||||||
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
|
import LocalStorageSource from "./LocalStorageSource";
|
||||||
|
|
||||||
|
export default class FeaturePipeline implements FeatureSource {
|
||||||
|
|
||||||
|
public features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
||||||
|
constructor(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], updater: FeatureSource) {
|
||||||
|
|
||||||
|
const overpassSource = new WayHandlingApplyingFeatureSource(flayers,
|
||||||
|
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, updater))
|
||||||
|
);
|
||||||
|
|
||||||
|
const amendedOverpassSource =
|
||||||
|
new RememberingSource(new LocalStorageSaver(
|
||||||
|
overpassSource
|
||||||
|
));
|
||||||
|
|
||||||
|
const merged = new FeatureSourceMerger([
|
||||||
|
amendedOverpassSource,
|
||||||
|
new FeatureDuplicatorPerLayer(flayers, State.state.changes),
|
||||||
|
new LocalStorageSource()
|
||||||
|
]);
|
||||||
|
merged.features.addCallbackAndRun(feats => console.log("Merged has",feats?.length))
|
||||||
|
|
||||||
|
const source =
|
||||||
|
new FilteringFeatureSource(
|
||||||
|
flayers,
|
||||||
|
State.state.locationControl,
|
||||||
|
merged
|
||||||
|
);
|
||||||
|
source.features.addCallbackAndRun(feats => console.log("Filtered has",feats?.length))
|
||||||
|
|
||||||
|
|
||||||
|
this.features = source.features;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,15 +9,20 @@ export default class FeatureSourceMerger implements FeatureSource {
|
||||||
constructor(sources: FeatureSource[]) {
|
constructor(sources: FeatureSource[]) {
|
||||||
this._sources = sources;
|
this._sources = sources;
|
||||||
const self = this;
|
const self = this;
|
||||||
for (const source of sources) {
|
for (let i = 0; i < sources.length; i++){
|
||||||
source.features.addCallback(() => self.Update());
|
let source = sources[i];
|
||||||
|
source.features.addCallback(() => {
|
||||||
|
self.Update();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
this.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Update() {
|
private Update() {
|
||||||
let all = {}; // Mapping 'id' -> {feature, freshness}
|
let all = {}; // Mapping 'id' -> {feature, freshness}
|
||||||
for (const source of this._sources) {
|
for (const source of this._sources) {
|
||||||
if(source?.features?.data === undefined){
|
if(source?.features?.data === undefined){
|
||||||
|
console.log("Not defined");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const f of source.features.data) {
|
for (const f of source.features.data) {
|
||||||
|
|
|
@ -74,6 +74,7 @@ export default class FilteringFeatureSource implements FeatureSource {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
Logic/FeatureSource/LocalStorageSaver.ts
Normal file
35
Logic/FeatureSource/LocalStorageSaver.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/***
|
||||||
|
* Saves all the features that are passed in to localstorage, so they can be retrieved on the next run
|
||||||
|
*
|
||||||
|
* Technically, more an Actor then a featuresource, but it fits more neatly this ay
|
||||||
|
*/
|
||||||
|
import FeatureSource from "./FeatureSource";
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
|
||||||
|
export default class LocalStorageSaver implements FeatureSource {
|
||||||
|
public static readonly storageKey: string = "cached-features";
|
||||||
|
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
||||||
|
constructor(source: FeatureSource) {
|
||||||
|
this.features = source.features;
|
||||||
|
|
||||||
|
this.features.addCallbackAndRun(features => {
|
||||||
|
if (features === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(features.length == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LocalStorageSaver.storageKey, JSON.stringify(features));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not save the features to local storage:", e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
22
Logic/FeatureSource/LocalStorageSource.ts
Normal file
22
Logic/FeatureSource/LocalStorageSource.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import FeatureSource from "./FeatureSource";
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import LocalStorageSaver from "./LocalStorageSaver";
|
||||||
|
|
||||||
|
export default class LocalStorageSource implements FeatureSource {
|
||||||
|
public features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
|
||||||
|
try {
|
||||||
|
const fromStorage = localStorage.getItem(LocalStorageSaver.storageKey);
|
||||||
|
if (fromStorage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const loaded = JSON.parse(fromStorage);
|
||||||
|
this.features.setData(loaded);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Could not load features from localStorage:", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { Utils } from "../Utils";
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
|
|
||||||
public static vNumber = "0.4.8";
|
public static vNumber = "0.4.9";
|
||||||
|
|
||||||
// The user journey states thresholds when a new feature gets unlocked
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
public static userJourney = {
|
public static userJourney = {
|
||||||
|
|
21
State.ts
21
State.ts
|
@ -125,11 +125,11 @@ export default class State {
|
||||||
this.layoutToUse.setData(layoutToUse);
|
this.layoutToUse.setData(layoutToUse);
|
||||||
|
|
||||||
const zoom = State.asFloat(
|
const zoom = State.asFloat(
|
||||||
QueryParameters.GetQueryParameter("z", "" + layoutToUse?.startZoom, "The initial/current zoom level")
|
QueryParameters.GetQueryParameter("z", "" +(layoutToUse?.startZoom ?? 1), "The initial/current zoom level")
|
||||||
.syncWith(LocalStorageSource.Get("zoom")));
|
.syncWith(LocalStorageSource.Get("zoom")));
|
||||||
const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + layoutToUse?.startLat, "The initial/current latitude")
|
const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude")
|
||||||
.syncWith(LocalStorageSource.Get("lat")));
|
.syncWith(LocalStorageSource.Get("lat")));
|
||||||
const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + layoutToUse?.startLon, "The initial/current longitude of the app")
|
const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + (layoutToUse?.startLon ?? 0), "The initial/current longitude of the app")
|
||||||
.syncWith(LocalStorageSource.Get("lon")));
|
.syncWith(LocalStorageSource.Get("lon")));
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,6 +188,9 @@ export default class State {
|
||||||
|
|
||||||
const testParam = QueryParameters.GetQueryParameter("test", "false",
|
const testParam = QueryParameters.GetQueryParameter("test", "false",
|
||||||
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org").data;
|
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org").data;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.osmConnection = new OsmConnection(
|
this.osmConnection = new OsmConnection(
|
||||||
testParam === "true",
|
testParam === "true",
|
||||||
QueryParameters.GetQueryParameter("oauth_token", undefined,
|
QueryParameters.GetQueryParameter("oauth_token", undefined,
|
||||||
|
@ -245,18 +248,6 @@ export default class State {
|
||||||
|
|
||||||
this.allElements = new ElementStorage();
|
this.allElements = new ElementStorage();
|
||||||
this.changes = new Changes();
|
this.changes = new Changes();
|
||||||
|
|
||||||
if (State.runningFromConsole) {
|
|
||||||
console.warn("running from console - not initializing map. Assuming test.html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (document.getElementById("leafletDiv") === null) {
|
|
||||||
console.warn("leafletDiv not found - not initializing map. Assuming test.html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default class ScrollableFullScreen extends UIElement {
|
||||||
State.state.selectedElement.setData(undefined);
|
State.state.selectedElement.setData(undefined);
|
||||||
}).SetClass("only-on-mobile")
|
}).SetClass("only-on-mobile")
|
||||||
.SetClass("featureinfobox-back-to-the-map")
|
.SetClass("featureinfobox-back-to-the-map")
|
||||||
title.SetClass("featureinfobox-title")
|
title.SetStyle("width: 100%; display: block;")
|
||||||
const ornament = new Combine([new Ornament().SetStyle("height:5em;")]).SetClass("only-on-mobile")
|
const ornament = new Combine([new Ornament().SetStyle("height:5em;")]).SetClass("only-on-mobile")
|
||||||
|
|
||||||
this._component = new Combine([
|
this._component = new Combine([
|
||||||
|
|
|
@ -55,7 +55,6 @@ export default class EditableTagRendering extends UIElement {
|
||||||
}
|
}
|
||||||
if (this._configuration.multiAnswer) {
|
if (this._configuration.multiAnswer) {
|
||||||
const atLeastOneMatch = this._configuration.mappings.some(mp =>TagUtils.MatchesMultiAnswer(mp.if, this._tags.data));
|
const atLeastOneMatch = this._configuration.mappings.some(mp =>TagUtils.MatchesMultiAnswer(mp.if, this._tags.data));
|
||||||
console.log("SOME MATCH?", atLeastOneMatch)
|
|
||||||
if (!atLeastOneMatch) {
|
if (!atLeastOneMatch) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default class ShowDataLayer {
|
||||||
const mp = leafletMap.data;
|
const mp = leafletMap.data;
|
||||||
|
|
||||||
const feats = features.data.map(ff => ff.feature);
|
const feats = features.data.map(ff => ff.feature);
|
||||||
|
|
||||||
let geoLayer = self.CreateGeojsonLayer(feats)
|
let geoLayer = self.CreateGeojsonLayer(feats)
|
||||||
if (layoutToUse.clustering.minNeededElements <= features.data.length) {
|
if (layoutToUse.clustering.minNeededElements <= features.data.length) {
|
||||||
const cl = window["L"]; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something
|
const cl = window["L"]; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something
|
||||||
|
@ -79,6 +80,7 @@ export default class ShowDataLayer {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +96,7 @@ export default class ShowDataLayer {
|
||||||
// We have to convert them to the appropriate icon
|
// We have to convert them to the appropriate icon
|
||||||
// Click handling is done in the next step
|
// Click handling is done in the next step
|
||||||
|
|
||||||
const tagSource = State.state.allElements.getEventSourceFor(feature);
|
const tagSource = State.state.allElements.addOrGetElement(feature)
|
||||||
const layer: LayerConfig = this._layerDict[feature._matching_layer_id];
|
const layer: LayerConfig = this._layerDict[feature._matching_layer_id];
|
||||||
|
|
||||||
const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));
|
const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));
|
||||||
|
|
Loading…
Reference in a new issue