Refactoring: improve caching
This commit is contained in:
parent
f203a1158d
commit
e36e9123f3
5 changed files with 91 additions and 20 deletions
|
@ -2,8 +2,50 @@ import { FeatureSource } from "../FeatureSource"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import TileLocalStorage from "./TileLocalStorage"
|
import TileLocalStorage from "./TileLocalStorage"
|
||||||
import { GeoOperations } from "../../GeoOperations"
|
import { GeoOperations } from "../../GeoOperations"
|
||||||
|
import FeaturePropertiesStore from "./FeaturePropertiesStore"
|
||||||
|
import { UIEventSource } from "../../UIEventSource"
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
|
|
||||||
|
class SingleTileSaver {
|
||||||
|
private readonly _storage: UIEventSource<Feature[]>
|
||||||
|
private readonly _registeredIds = new Set<string>()
|
||||||
|
private readonly _featureProperties: FeaturePropertiesStore
|
||||||
|
private readonly _isDirty = new UIEventSource(false)
|
||||||
|
constructor(
|
||||||
|
storage: UIEventSource<Feature[]> & { flush: () => void },
|
||||||
|
featureProperties: FeaturePropertiesStore
|
||||||
|
) {
|
||||||
|
this._storage = storage
|
||||||
|
this._featureProperties = featureProperties
|
||||||
|
this._isDirty.stabilized(1000).addCallbackD((isDirty) => {
|
||||||
|
if (!isDirty) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 'isDirty' is set when tags of some object have changed
|
||||||
|
storage.flush()
|
||||||
|
this._isDirty.setData(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveFeatures(features: Feature[]) {
|
||||||
|
if (Utils.sameList(features, this._storage.data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const feature of features) {
|
||||||
|
const id = feature.properties.id
|
||||||
|
if (this._registeredIds.has(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
this._featureProperties.getStore(id)?.addCallbackAndRunD(() => {
|
||||||
|
this._isDirty.setData(true)
|
||||||
|
})
|
||||||
|
this._registeredIds.add(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._storage.setData(features)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Saves all the features that are passed in to localstorage, so they can be retrieved on the next run
|
* Saves all the features that are passed in to localstorage, so they can be retrieved on the next run
|
||||||
*
|
*
|
||||||
|
@ -12,16 +54,27 @@ import { Utils } from "../../../Utils"
|
||||||
* Also see the sibling class
|
* Also see the sibling class
|
||||||
*/
|
*/
|
||||||
export default class SaveFeatureSourceToLocalStorage {
|
export default class SaveFeatureSourceToLocalStorage {
|
||||||
constructor(layername: string, zoomlevel: number, features: FeatureSource) {
|
constructor(
|
||||||
|
layername: string,
|
||||||
|
zoomlevel: number,
|
||||||
|
features: FeatureSource,
|
||||||
|
featureProperties: FeaturePropertiesStore
|
||||||
|
) {
|
||||||
const storage = TileLocalStorage.construct<Feature[]>(layername)
|
const storage = TileLocalStorage.construct<Feature[]>(layername)
|
||||||
|
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
|
||||||
features.features.addCallbackAndRunD((features) => {
|
features.features.addCallbackAndRunD((features) => {
|
||||||
const sliced = GeoOperations.slice(zoomlevel, features)
|
const sliced = GeoOperations.slice(zoomlevel, features)
|
||||||
|
|
||||||
sliced.forEach((features, tileIndex) => {
|
sliced.forEach((features, tileIndex) => {
|
||||||
|
let tileSaver = singleTileSavers.get(tileIndex)
|
||||||
|
if (tileSaver === undefined) {
|
||||||
const src = storage.getTileSource(tileIndex)
|
const src = storage.getTileSource(tileIndex)
|
||||||
if (Utils.sameList(src.data, features)) {
|
tileSaver = new SingleTileSaver(src, featureProperties)
|
||||||
return
|
singleTileSavers.set(tileIndex, tileSaver)
|
||||||
}
|
}
|
||||||
src.setData(features)
|
// Don't cache not-uploaded features yet - they'll be cached when the receive their id
|
||||||
|
features = features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
|
||||||
|
tileSaver.saveFeatures(features)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import { UIEventSource } from "../../UIEventSource"
|
||||||
/**
|
/**
|
||||||
* A class which allows to read/write a tile to local storage.
|
* A class which allows to read/write a tile to local storage.
|
||||||
*
|
*
|
||||||
* Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage
|
* Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage.
|
||||||
|
*
|
||||||
|
* Note: OSM-features with a negative id are ignored
|
||||||
*/
|
*/
|
||||||
export default class TileLocalStorage<T> {
|
export default class TileLocalStorage<T> {
|
||||||
private static perLayer: Record<string, TileLocalStorage<any>> = {}
|
private static perLayer: Record<string, TileLocalStorage<any>> = {}
|
||||||
private readonly _layername: string
|
private readonly _layername: string
|
||||||
private readonly cachedSources: Record<number, UIEventSource<T>> = {}
|
private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {}
|
||||||
|
|
||||||
private constructor(layername: string) {
|
private constructor(layername: string) {
|
||||||
this._layername = layername
|
this._layername = layername
|
||||||
|
@ -27,24 +29,26 @@ export default class TileLocalStorage<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a UIEventSource element which is synced with localStorage
|
* Constructs a UIEventSource element which is synced with localStorage.
|
||||||
* @param layername
|
* Supports 'flush'
|
||||||
* @param tileIndex
|
|
||||||
*/
|
*/
|
||||||
public getTileSource(tileIndex: number): UIEventSource<T> {
|
public getTileSource(tileIndex: number): UIEventSource<T> & { flush: () => void } {
|
||||||
const cached = this.cachedSources[tileIndex]
|
const cached = this.cachedSources[tileIndex]
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
const src = UIEventSource.FromPromise(this.GetIdb(tileIndex))
|
const src = <UIEventSource<T> & { flush: () => void }>(
|
||||||
|
UIEventSource.FromPromise(this.GetIdb(tileIndex))
|
||||||
|
)
|
||||||
|
src.flush = () => this.SetIdb(tileIndex, src.data)
|
||||||
src.addCallbackD((data) => this.SetIdb(tileIndex, data))
|
src.addCallbackD((data) => this.SetIdb(tileIndex, data))
|
||||||
this.cachedSources[tileIndex] = src
|
this.cachedSources[tileIndex] = src
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
private SetIdb(tileIndex: number, data): void {
|
private async SetIdb(tileIndex: number, data): Promise<void> {
|
||||||
try {
|
try {
|
||||||
IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
|
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
"Could not save tile to indexed-db: ",
|
"Could not save tile to indexed-db: ",
|
||||||
|
@ -52,7 +56,9 @@ export default class TileLocalStorage<T> {
|
||||||
"tileIndex is:",
|
"tileIndex is:",
|
||||||
tileIndex,
|
tileIndex,
|
||||||
"for layer",
|
"for layer",
|
||||||
this._layername
|
this._layername,
|
||||||
|
"data is",
|
||||||
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,14 @@ export default class LocalStorageFeatureSource extends DynamicTileSource {
|
||||||
const storage = TileLocalStorage.construct<Feature[]>(layername)
|
const storage = TileLocalStorage.construct<Feature[]>(layername)
|
||||||
super(
|
super(
|
||||||
zoomlevel,
|
zoomlevel,
|
||||||
(tileIndex) => new StaticFeatureSource(storage.getTileSource(tileIndex)),
|
(tileIndex) =>
|
||||||
|
new StaticFeatureSource(
|
||||||
|
storage
|
||||||
|
.getTileSource(tileIndex)
|
||||||
|
.map((features) =>
|
||||||
|
features?.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
|
||||||
|
)
|
||||||
|
),
|
||||||
mapProperties,
|
mapProperties,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,11 +38,11 @@ export class IdbLocalStorage {
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SetDirectly(key: string, value) {
|
public static SetDirectly(key: string, value): Promise<void> {
|
||||||
idb.set(key, value)
|
return idb.set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
static GetDirectly(key: string) {
|
static GetDirectly(key: string): Promise<void> {
|
||||||
return idb.get(key)
|
return idb.get(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.perLayer = perLayer.perLayer
|
this.perLayer = perLayer.perLayer
|
||||||
|
|
||||||
this.perLayer.forEach((fs) => {
|
this.perLayer.forEach((fs) => {
|
||||||
new SaveFeatureSourceToLocalStorage(fs.layer.layerDef.id, 15, fs)
|
new SaveFeatureSourceToLocalStorage(
|
||||||
|
fs.layer.layerDef.id,
|
||||||
|
15,
|
||||||
|
fs,
|
||||||
|
this.featureProperties
|
||||||
|
)
|
||||||
|
|
||||||
const filtered = new FilteringFeatureSource(
|
const filtered = new FilteringFeatureSource(
|
||||||
fs.layer,
|
fs.layer,
|
||||||
|
|
Loading…
Reference in a new issue