Add caching into local storage for a faster map experience

This commit is contained in:
pietervdvn 2021-01-15 00:29:07 +01:00
parent 3a2d654ac3
commit f33fe081d0
12 changed files with 128 additions and 41 deletions

View file

@ -27,21 +27,16 @@ import Img from "./UI/Base/Img";
import UserDetails from "./Logic/Osm/OsmConnection";
import Attribution from "./UI/BigComponents/Attribution";
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 LayerResetter from "./Logic/Actors/LayerResetter";
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
import LayerControlPanel from "./UI/BigComponents/LayerControlPanel";
import FeatureSwitched from "./UI/Base/FeatureSwitched";
import FeatureDuplicatorPerLayer from "./Logic/FeatureSource/FeatureDuplicatorPerLayer";
import LayerConfig from "./Customizations/JSON/LayerConfig";
import ShowDataLayer from "./UI/ShowDataLayer";
import Hash from "./Logic/Web/Hash";
import HistoryHandling from "./Logic/Actors/HistoryHandling";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
export class InitUiElements {
@ -294,7 +289,7 @@ export class InitUiElements {
const fullOptions2 = new FullWelcomePaneWithTabs();
if (Hash.hash.data === undefined) {
State.state.fullScreenMessage.setData({content: fullOptions2, hashText: "welcome"})
}
Svg.help_svg()
@ -405,20 +400,9 @@ export class InitUiElements {
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
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 }[]) => {
let features = featuresFreshness.map(ff => ff.feature);
features.forEach(feature => {

View file

@ -130,7 +130,7 @@ export default class UpdateFromOverpass implements FeatureSource{
const w = Math.max(-180, bounds.getWest() - diff);
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);
const self = this;

View 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;
}
}

View file

@ -9,15 +9,20 @@ export default class FeatureSourceMerger implements FeatureSource {
constructor(sources: FeatureSource[]) {
this._sources = sources;
const self = this;
for (const source of sources) {
source.features.addCallback(() => self.Update());
for (let i = 0; i < sources.length; i++){
let source = sources[i];
source.features.addCallback(() => {
self.Update();
});
}
this.Update();
}
private Update() {
let all = {}; // Mapping 'id' -> {feature, freshness}
for (const source of this._sources) {
if(source?.features?.data === undefined){
console.log("Not defined");
continue;
}
for (const f of source.features.data) {

View file

@ -74,6 +74,7 @@ export default class FilteringFeatureSource implements FeatureSource {
update();
});
update();
}

View 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)
}
})
}
}

View 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)
}
}
}

View file

@ -2,7 +2,7 @@ import { Utils } from "../Utils";
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
public static userJourney = {

View file

@ -125,11 +125,11 @@ export default class State {
this.layoutToUse.setData(layoutToUse);
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")));
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")));
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")));
@ -188,6 +188,9 @@ export default class State {
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;
this.osmConnection = new OsmConnection(
testParam === "true",
QueryParameters.GetQueryParameter("oauth_token", undefined,
@ -245,18 +248,6 @@ export default class State {
this.allElements = new ElementStorage();
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> {

View file

@ -18,7 +18,7 @@ export default class ScrollableFullScreen extends UIElement {
State.state.selectedElement.setData(undefined);
}).SetClass("only-on-mobile")
.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")
this._component = new Combine([

View file

@ -55,7 +55,6 @@ export default class EditableTagRendering extends UIElement {
}
if (this._configuration.multiAnswer) {
const atLeastOneMatch = this._configuration.mappings.some(mp =>TagUtils.MatchesMultiAnswer(mp.if, this._tags.data));
console.log("SOME MATCH?", atLeastOneMatch)
if (!atLeastOneMatch) {
return "";
}

View file

@ -41,6 +41,7 @@ export default class ShowDataLayer {
const mp = leafletMap.data;
const feats = features.data.map(ff => ff.feature);
let geoLayer = self.CreateGeojsonLayer(feats)
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
@ -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
// 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 style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));