diff --git a/src/Logic/Actors/SelectedElementTagsUpdater.ts b/src/Logic/Actors/SelectedElementTagsUpdater.ts index 37297111d..895444210 100644 --- a/src/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/src/Logic/Actors/SelectedElementTagsUpdater.ts @@ -1,26 +1,12 @@ /** * This actor will download the latest version of the selected element from OSM and update the tags if necessary. */ -import { UIEventSource } from "../UIEventSource" -import { Changes } from "../Osm/Changes" -import { OsmConnection } from "../Osm/OsmConnection" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import SimpleMetaTagger from "../SimpleMetaTagger" -import { Feature } from "geojson" import { OsmTags } from "../../Models/OsmFeature" -import OsmObjectDownloader from "../Osm/OsmObjectDownloader" -import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { Utils } from "../../Utils" - -interface TagsUpdaterState { - selectedElement: UIEventSource - featureProperties: { getStore: (id: string) => UIEventSource> } - changes: Changes - osmConnection: OsmConnection - layout: LayoutConfig - osmObjectDownloader: OsmObjectDownloader - indexedFeatures: IndexedFeatureSource -} +import ThemeViewState from "../../Models/ThemeViewState" +import { BBox } from "../BBox" +import { Feature } from "geojson" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -31,19 +17,21 @@ export default class SelectedElementTagsUpdater { "uid", "id", ]) + private readonly state: ThemeViewState - constructor(state: TagsUpdaterState) { + constructor(state: ThemeViewState) { + this.state = state state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => { if (!isLoggedIn && !Utils.runningFromConsole) { return } - this.installCallback(state) + this.installCallback() // We only have to do this once... return true }) } - public static applyUpdate(latestTags: OsmTags, id: string, state: TagsUpdaterState) { + public static applyUpdate(latestTags: OsmTags, id: string, state: ThemeViewState) { try { const leftRightSensitive = state.layout.isLeftRightSensitive() @@ -120,8 +108,13 @@ export default class SelectedElementTagsUpdater { console.error("Updating the tags of selected element ", id, "failed due to", e) } } - - private installCallback(state: TagsUpdaterState) { + private invalidateCache(s: Feature) { + const state = this.state + const wasPartOfLayer = state.layout.getMatchingLayer(s.properties) + state.toCacheSavers.get(wasPartOfLayer.id).invalidateCacheAround(BBox.get(s)) + } + private installCallback() { + const state = this.state state.selectedElement.addCallbackAndRunD(async (s) => { let id = s.properties?.id if (!id) { @@ -146,9 +139,9 @@ export default class SelectedElementTagsUpdater { const osmObject = await state.osmObjectDownloader.DownloadObjectAsync(id) if (osmObject === "deleted") { console.debug("The current selected element has been deleted upstream!", id) + this.invalidateCache(s) const currentTagsSource = state.featureProperties.getStore(id) currentTagsSource.data["_deleted"] = "yes" - currentTagsSource.addCallbackAndRun((tags) => console.trace("Tags are", tags)) currentTagsSource.ping() return } @@ -158,6 +151,7 @@ export default class SelectedElementTagsUpdater { const oldGeometry = oldFeature?.geometry if (oldGeometry !== undefined && !Utils.SameObject(newGeometry, oldGeometry)) { console.log("Detected a difference in geometry for ", id) + this.invalidateCache(s) oldFeature.geometry = newGeometry state.featureProperties.getStore(id)?.ping() } diff --git a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index fdfa4e21d..bc26ce3f5 100644 --- a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -5,6 +5,8 @@ import { GeoOperations } from "../../GeoOperations" import FeaturePropertiesStore from "./FeaturePropertiesStore" import { UIEventSource } from "../../UIEventSource" import { Utils } from "../../../Utils" +import { Tiles } from "../../../Models/TileRange" +import { BBox } from "../../BBox" class SingleTileSaver { private readonly _storage: UIEventSource @@ -54,6 +56,8 @@ class SingleTileSaver { * Also see the sibling class */ export default class SaveFeatureSourceToLocalStorage { + public readonly storage: TileLocalStorage + private zoomlevel: number constructor( backend: string, layername: string, @@ -62,7 +66,9 @@ export default class SaveFeatureSourceToLocalStorage { featureProperties: FeaturePropertiesStore, maxCacheAge: number ) { + this.zoomlevel = zoomlevel const storage = TileLocalStorage.construct(backend, layername, maxCacheAge) + this.storage = storage const singleTileSavers: Map = new Map() features.features.addCallbackAndRunD((features) => { const sliced = GeoOperations.slice(zoomlevel, features) @@ -80,4 +86,12 @@ export default class SaveFeatureSourceToLocalStorage { }) }) } + + public invalidateCacheAround(bbox: BBox) { + const range = Tiles.tileRangeFrom(bbox, this.zoomlevel) + Tiles.MapRange(range, (x, y) => { + const index = Tiles.tile_index(this.zoomlevel, x, y) + this.storage.invalidate(index) + }) + } } diff --git a/src/Logic/FeatureSource/Actors/TileLocalStorage.ts b/src/Logic/FeatureSource/Actors/TileLocalStorage.ts index a02f03e10..5bac139d4 100644 --- a/src/Logic/FeatureSource/Actors/TileLocalStorage.ts +++ b/src/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -1,5 +1,6 @@ import { IdbLocalStorage } from "../../Web/IdbLocalStorage" import { UIEventSource } from "../../UIEventSource" +import { Tiles } from "../../../Models/TileRange" /** * A class which allows to read/write a tile to local storage. @@ -91,9 +92,17 @@ export default class TileLocalStorage { await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex + "_date") ) const maxAge = this._maxAgeSeconds - const timeDiff = Date.now() - date + const timeDiff = (Date.now() - date) / 1000 if (timeDiff >= maxAge) { - console.debug("Dropping cache for", this._layername, tileIndex, "out of date") + console.debug( + "Dropping cache for", + this._layername, + tileIndex, + "out of date. Max allowed age is", + maxAge, + "current age is", + timeDiff + ) await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, undefined) return undefined @@ -102,7 +111,8 @@ export default class TileLocalStorage { return data } - invalidate(zoomlevel: number, tileIndex) { + public invalidate(tileIndex: number) { + console.log("Invalidated tile", tileIndex) this.getTileSource(tileIndex).setData(undefined) } } diff --git a/src/Logic/FeatureSource/Sources/LayoutSource.ts b/src/Logic/FeatureSource/Sources/LayoutSource.ts index 72f90d772..36f1baef8 100644 --- a/src/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/src/Logic/FeatureSource/Sources/LayoutSource.ts @@ -27,7 +27,7 @@ export default class LayoutSource extends FeatureSourceMerger { private readonly supportsForceDownload: UpdatableFeatureSource[] private readonly fromCache: Map - private static readonly fromCacheZoomLevel = 15 + public static readonly fromCacheZoomLevel = 15 constructor( layers: LayerConfig[], featureSwitches: FeatureSwitchState, diff --git a/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts index 11ce41080..5efb4c552 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -27,14 +27,14 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { options?.maxAge ?? 24 * 60 * 60 ) super( - new ImmutableStore(zoomlevel), + new ImmutableStore(zoomlevel), layer.minzoom, (tileIndex) => new StaticFeatureSource( storage.getTileSource(tileIndex).mapD((features) => { if (features.length === undefined) { console.trace("These are not features:", features) - storage.invalidate(zoomlevel, tileIndex) + storage.invalidate(tileIndex) return [] } return features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 50fe06d71..0e0bdab62 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -146,6 +146,7 @@ export default class ThemeViewState implements SpecialVisualizationState { * Triggered by navigating the map with arrows or by pressing 'space' or 'enter' */ public readonly visualFeedback: UIEventSource = new UIEventSource(false) + public readonly toCacheSavers: ReadonlyMap constructor(layout: LayoutConfig, mvtAvailableLayers: Set) { Utils.initDomPurify() @@ -295,16 +296,6 @@ export default class ThemeViewState implements SpecialVisualizationState { ) this.perLayer = perLayer.perLayer } - this.perLayer.forEach((fs) => { - new SaveFeatureSourceToLocalStorage( - this.osmConnection.Backend(), - fs.layer.layerDef.id, - 15, - fs, - this.featureProperties, - fs.layer.layerDef.maxAgeOfCache - ) - }) this.floors = this.featuresInView.features.stabilized(500).map((features) => { if (!features) { @@ -366,6 +357,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.favourites = new FavouritesFeatureSource(this) this.featureSummary = this.setupSummaryLayer() + this.toCacheSavers = this.initSaveToLocalStorage() this.initActors() this.drawSpecialLayers() this.initHotkeys() @@ -391,6 +383,25 @@ export default class ThemeViewState implements SpecialVisualizationState { }) } + public initSaveToLocalStorage() { + const toLocalStorage = new Map() + this.perLayer.forEach((fs, layerId) => { + if (fs.layer.layerDef.source.geojsonSource !== undefined) { + return // We don't cache external data layers + } + console.log("Setting up a local store feature sink for", layerId) + const storage = new SaveFeatureSourceToLocalStorage( + this.osmConnection.Backend(), + fs.layer.layerDef.id, + LayoutSource.fromCacheZoomLevel, + fs, + this.featureProperties, + fs.layer.layerDef.maxAgeOfCache + ) + toLocalStorage.set(layerId, storage) + }) + return toLocalStorage + } public showNormalDataOn(map: Store): ReadonlyMap { const filteringFeatureSource = new Map() this.perLayer.forEach((fs, layerName) => {