forked from MapComplete/MapComplete
Merge branch 'master' into develop
This commit is contained in:
commit
5215662a0c
67 changed files with 1571 additions and 1276 deletions
|
@ -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<Feature>
|
||||
featureProperties: { getStore: (id: string) => UIEventSource<Record<string, string>> }
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -45,15 +45,16 @@ export class BBox {
|
|||
])
|
||||
}
|
||||
|
||||
static get(feature): BBox {
|
||||
if (feature.bbox?.overlapsWith === undefined) {
|
||||
static get(feature: Feature): BBox {
|
||||
const f = <any>feature
|
||||
if (f.bbox?.overlapsWith === undefined) {
|
||||
const turfBbox: number[] = turf.bbox(feature)
|
||||
feature.bbox = new BBox([
|
||||
f["bbox"] = new BBox([
|
||||
[turfBbox[0], turfBbox[1]],
|
||||
[turfBbox[2], turfBbox[3]],
|
||||
])
|
||||
}
|
||||
return feature.bbox
|
||||
return f["bbox"]
|
||||
}
|
||||
|
||||
static bboxAroundAll(bboxes: BBox[]): BBox {
|
||||
|
|
|
@ -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<Feature[]>
|
||||
|
@ -54,6 +56,8 @@ class SingleTileSaver {
|
|||
* Also see the sibling class
|
||||
*/
|
||||
export default class SaveFeatureSourceToLocalStorage {
|
||||
public readonly storage: TileLocalStorage<Feature[]>
|
||||
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<Feature[]>(backend, layername, maxCacheAge)
|
||||
this.storage = storage
|
||||
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> {
|
|||
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<T> {
|
|||
return <any>data
|
||||
}
|
||||
|
||||
invalidate(zoomlevel: number, tileIndex) {
|
||||
public invalidate(tileIndex: number) {
|
||||
console.log("Invalidated tile", tileIndex)
|
||||
this.getTileSource(tileIndex).setData(undefined)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
|
||||
private readonly supportsForceDownload: UpdatableFeatureSource[]
|
||||
|
||||
private readonly fromCache: Map<string, LocalStorageFeatureSource>
|
||||
public static readonly fromCacheZoomLevel = 15
|
||||
constructor(
|
||||
layers: LayerConfig[],
|
||||
featureSwitches: FeatureSwitchState,
|
||||
|
@ -43,13 +45,21 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
|
||||
const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined)
|
||||
const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined)
|
||||
const fromCache = osmLayers.map(
|
||||
(l) =>
|
||||
new LocalStorageFeatureSource(backend, l, 15, mapProperties, {
|
||||
isActive: isDisplayed(l.id),
|
||||
maxAge: l.maxAgeOfCache,
|
||||
})
|
||||
)
|
||||
const fromCache = new Map<string, LocalStorageFeatureSource>()
|
||||
for (const layer of osmLayers) {
|
||||
const src = new LocalStorageFeatureSource(
|
||||
backend,
|
||||
layer,
|
||||
LayoutSource.fromCacheZoomLevel,
|
||||
mapProperties,
|
||||
{
|
||||
isActive: isDisplayed(layer.id),
|
||||
maxAge: layer.maxAgeOfCache,
|
||||
}
|
||||
)
|
||||
fromCache.set(layer.id, src)
|
||||
}
|
||||
|
||||
const mvtSources: UpdatableFeatureSource[] = osmLayers
|
||||
.filter((f) => mvtAvailableLayers.has(f.id))
|
||||
.map((l) => LayoutSource.setupMvtSource(l, mapProperties, isDisplayed(l.id)))
|
||||
|
@ -92,9 +102,10 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id))
|
||||
)
|
||||
|
||||
super(...geojsonSources, ...fromCache, ...mvtSources, ...nonMvtSources)
|
||||
super(...geojsonSources, ...Array.from(fromCache.values()), ...mvtSources, ...nonMvtSources)
|
||||
|
||||
this.isLoading = isLoading
|
||||
this.fromCache = fromCache
|
||||
supportsForceDownload.push(...geojsonSources)
|
||||
supportsForceDownload.push(...mvtSources) // Non-mvt sources are handled by overpass
|
||||
this.supportsForceDownload = supportsForceDownload
|
||||
|
|
|
@ -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]+/))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue