forked from MapComplete/MapComplete
Better tracking of cached data, only load data if needed
This commit is contained in:
parent
32cbd6e2c1
commit
4f456e8a7f
13 changed files with 349 additions and 185 deletions
|
@ -207,6 +207,9 @@ export default class GeoLocationHandler extends VariableUiElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
const map = self._leafletMap.data;
|
const map = self._leafletMap.data;
|
||||||
|
if(map === undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newMarker = L.marker(location.latlng, {icon: icon});
|
const newMarker = L.marker(location.latlng, {icon: icon});
|
||||||
newMarker.addTo(map);
|
newMarker.addTo(map);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import Loc from "../../Models/Loc";
|
|
||||||
import {Or} from "../Tags/Or";
|
import {Or} from "../Tags/Or";
|
||||||
import {Overpass} from "../Osm/Overpass";
|
import {Overpass} from "../Osm/Overpass";
|
||||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||||
|
@ -9,6 +8,8 @@ import SimpleMetaTagger from "../SimpleMetaTagger";
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||||
import RelationsTracker from "../Osm/RelationsTracker";
|
import RelationsTracker from "../Osm/RelationsTracker";
|
||||||
import {BBox} from "../BBox";
|
import {BBox} from "../BBox";
|
||||||
|
import Loc from "../../Models/Loc";
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
|
|
||||||
|
|
||||||
export default class OverpassFeatureSource implements FeatureSource {
|
export default class OverpassFeatureSource implements FeatureSource {
|
||||||
|
@ -28,14 +29,7 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
|
|
||||||
|
|
||||||
private readonly retries: UIEventSource<number> = new UIEventSource<number>(0);
|
private readonly retries: UIEventSource<number> = new UIEventSource<number>(0);
|
||||||
/**
|
|
||||||
* The previous bounds for which the query has been run at the given zoom level
|
|
||||||
*
|
|
||||||
* Note that some layers only activate on a certain zoom level.
|
|
||||||
* If the map location changes, we check for each layer if it is loaded:
|
|
||||||
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
|
|
||||||
*/
|
|
||||||
private readonly _previousBounds: Map<number, BBox[]> = new Map<number, BBox[]>();
|
|
||||||
private readonly state: {
|
private readonly state: {
|
||||||
readonly locationControl: UIEventSource<Loc>,
|
readonly locationControl: UIEventSource<Loc>,
|
||||||
readonly layoutToUse: LayoutConfig,
|
readonly layoutToUse: LayoutConfig,
|
||||||
|
@ -44,11 +38,8 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
readonly currentBounds: UIEventSource<BBox>
|
readonly currentBounds: UIEventSource<BBox>
|
||||||
}
|
}
|
||||||
private readonly _isActive: UIEventSource<boolean>;
|
private readonly _isActive: UIEventSource<boolean>;
|
||||||
private _onUpdated?: (bbox: BBox, dataFreshness: Date) => void;
|
private readonly onBboxLoaded: (bbox: BBox, date: Date, layers: LayerConfig[]) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* The most important layer should go first, as that one gets first pick for the questions
|
|
||||||
*/
|
|
||||||
constructor(
|
constructor(
|
||||||
state: {
|
state: {
|
||||||
readonly locationControl: UIEventSource<Loc>,
|
readonly locationControl: UIEventSource<Loc>,
|
||||||
|
@ -60,68 +51,25 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
},
|
},
|
||||||
options?: {
|
options?: {
|
||||||
isActive?: UIEventSource<boolean>,
|
isActive?: UIEventSource<boolean>,
|
||||||
onUpdated?: (bbox: BBox, freshness: Date) => void,
|
relationTracker: RelationsTracker,
|
||||||
relationTracker: RelationsTracker
|
onBboxLoaded?: (bbox: BBox, date: Date, layers: LayerConfig[]) => void
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
this.state = state
|
this.state = state
|
||||||
this._isActive = options.isActive;
|
this._isActive = options.isActive;
|
||||||
this._onUpdated = options.onUpdated;
|
this.onBboxLoaded = options.onBboxLoaded
|
||||||
this.relationsTracker = options.relationTracker
|
this.relationsTracker = options.relationTracker
|
||||||
const location = state.locationControl
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
for (let i = 0; i < 25; i++) {
|
|
||||||
// This update removes all data on all layers -> erase the map on lower levels too
|
|
||||||
this._previousBounds.set(i, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
location.addCallback(() => {
|
|
||||||
self.update()
|
|
||||||
});
|
|
||||||
|
|
||||||
state.currentBounds.addCallback(_ => {
|
state.currentBounds.addCallback(_ => {
|
||||||
self.update()
|
self.update()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private GetFilter(interpreterUrl: string): Overpass {
|
private GetFilter(interpreterUrl: string, layersToDownload: LayerConfig[]): Overpass {
|
||||||
let filters: TagsFilter[] = [];
|
let filters: TagsFilter[] = [];
|
||||||
let extraScripts: string[] = [];
|
let extraScripts: string[] = [];
|
||||||
for (const layer of this.state.layoutToUse.layers) {
|
for (const layer of layersToDownload) {
|
||||||
if (typeof (layer) === "string") {
|
|
||||||
throw "A layer was not expanded!"
|
|
||||||
}
|
|
||||||
if (this.state.locationControl.data.zoom < layer.minzoom) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (layer.doNotDownload) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (layer.source.geojsonSource !== undefined) {
|
|
||||||
// Not our responsibility to download this layer!
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check if data for this layer has already been loaded
|
|
||||||
let previouslyLoaded = false;
|
|
||||||
for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) {
|
|
||||||
const previousLoadedBounds = this._previousBounds.get(z);
|
|
||||||
if (previousLoadedBounds === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const previousLoadedBound of previousLoadedBounds) {
|
|
||||||
previouslyLoaded = previouslyLoaded || this.state.currentBounds.data.isContainedIn(previousLoadedBound);
|
|
||||||
if (previouslyLoaded) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (previouslyLoaded) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (layer.source.overpassScript !== undefined) {
|
if (layer.source.overpassScript !== undefined) {
|
||||||
extraScripts.push(layer.source.overpassScript)
|
extraScripts.push(layer.source.overpassScript)
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,17 +88,17 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
if (!this._isActive.data) {
|
if (!this._isActive.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const self = this
|
const self = this;
|
||||||
this.updateAsync().then(bboxAndDate => {
|
this.updateAsync().then(bboxDate => {
|
||||||
if (bboxAndDate === undefined || self._onUpdated === undefined) {
|
if(bboxDate === undefined || self.onBboxLoaded === undefined){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [bbox, date] = bboxAndDate
|
const [bbox, date, layers] = bboxDate
|
||||||
self._onUpdated(bbox, date);
|
self.onBboxLoaded(bbox, date, layers)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAsync(): Promise<[BBox, Date]> {
|
private async updateAsync(): Promise<[BBox, Date, LayerConfig[]]> {
|
||||||
if (this.runningQuery.data) {
|
if (this.runningQuery.data) {
|
||||||
console.log("Still running a query, not updating");
|
console.log("Still running a query, not updating");
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -167,6 +115,26 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
|
||||||
|
const layersToDownload = []
|
||||||
|
for (const layer of this.state.layoutToUse.layers) {
|
||||||
|
|
||||||
|
if (typeof (layer) === "string") {
|
||||||
|
throw "A layer was not expanded!"
|
||||||
|
}
|
||||||
|
if(this.state.locationControl.data.zoom < layer.minzoom){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (layer.doNotDownload) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (layer.source.geojsonSource !== undefined) {
|
||||||
|
// Not our responsibility to download this layer!
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
layersToDownload.push(layer)
|
||||||
|
}
|
||||||
|
|
||||||
let data: any = undefined
|
let data: any = undefined
|
||||||
let date: Date = undefined
|
let date: Date = undefined
|
||||||
|
@ -175,7 +143,8 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
const overpass = this.GetFilter(overpassUrls[lastUsed]);
|
|
||||||
|
const overpass = this.GetFilter(overpassUrls[lastUsed], layersToDownload);
|
||||||
|
|
||||||
if (overpass === undefined) {
|
if (overpass === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -208,14 +177,11 @@ export default class OverpassFeatureSource implements FeatureSource {
|
||||||
}
|
}
|
||||||
} while (data === undefined);
|
} while (data === undefined);
|
||||||
|
|
||||||
const z = Math.floor(this.state.locationControl.data.zoom ?? 0);
|
|
||||||
self._previousBounds.get(z).push(bounds);
|
|
||||||
self.retries.setData(0);
|
self.retries.setData(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date));
|
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date));
|
||||||
self.features.setData(data.features.map(f => ({feature: f, freshness: date})));
|
self.features.setData(data.features.map(f => ({feature: f, freshness: date})));
|
||||||
return [bounds, date];
|
return [bounds, date, layersToDownload];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Got the overpass response, but could not process it: ", e, e.stack)
|
console.error("Got the overpass response, but could not process it: ", e, e.stack)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -7,28 +7,30 @@ import {FeatureSourceForLayer} from "../FeatureSource";
|
||||||
|
|
||||||
export default class SaveTileToLocalStorageActor {
|
export default class SaveTileToLocalStorageActor {
|
||||||
public static readonly storageKey: string = "cached-features";
|
public static readonly storageKey: string = "cached-features";
|
||||||
public static readonly formatVersion : string = "1"
|
public static readonly formatVersion: string = "1"
|
||||||
|
|
||||||
constructor(source: FeatureSourceForLayer, tileIndex: number) {
|
constructor(source: FeatureSourceForLayer, tileIndex: number) {
|
||||||
source.features.addCallbackAndRunD(features => {
|
source.features.addCallbackAndRunD(features => {
|
||||||
const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}`
|
const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}`
|
||||||
const now = new Date().getTime()
|
const now = new Date()
|
||||||
|
|
||||||
if (features.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, JSON.stringify(features));
|
if (features.length > 0) {
|
||||||
localStorage.setItem(key + "-time", JSON.stringify(now))
|
localStorage.setItem(key, JSON.stringify(features));
|
||||||
localStorage.setItem(key+"-format", SaveTileToLocalStorageActor.formatVersion)
|
}
|
||||||
|
// We _still_ write the time to know that this tile is empty!
|
||||||
|
SaveTileToLocalStorageActor.MarkVisited(source.layer.layerDef.id, tileIndex, now)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Could not save the features to local storage:", e)
|
console.warn("Could not save the features to local storage:", e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static MarkVisited(layerId: string, tileId: number, freshness: Date){
|
||||||
|
const key = `${SaveTileToLocalStorageActor.storageKey}-${layerId}-${tileId}`
|
||||||
|
localStorage.setItem(key + "-time", JSON.stringify(freshness.getTime()))
|
||||||
|
localStorage.setItem(key + "-format", SaveTileToLocalStorageActor.formatVersion)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -25,6 +25,7 @@ import {BBox} from "../BBox";
|
||||||
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
|
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
|
||||||
import {OsmConnection} from "../Osm/OsmConnection";
|
import {OsmConnection} from "../Osm/OsmConnection";
|
||||||
import {Tiles} from "../../Models/TileRange";
|
import {Tiles} from "../../Models/TileRange";
|
||||||
|
import TileFreshnessCalculator from "./TileFreshnessCalculator";
|
||||||
|
|
||||||
|
|
||||||
export default class FeaturePipeline {
|
export default class FeaturePipeline {
|
||||||
|
@ -38,9 +39,27 @@ export default class FeaturePipeline {
|
||||||
public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
|
public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
|
||||||
|
|
||||||
private readonly overpassUpdater: OverpassFeatureSource
|
private readonly overpassUpdater: OverpassFeatureSource
|
||||||
|
private state: {
|
||||||
|
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||||
|
readonly locationControl: UIEventSource<Loc>,
|
||||||
|
readonly selectedElement: UIEventSource<any>,
|
||||||
|
readonly changes: Changes,
|
||||||
|
readonly layoutToUse: LayoutConfig,
|
||||||
|
readonly leafletMap: any,
|
||||||
|
readonly overpassUrl: UIEventSource<string[]>;
|
||||||
|
readonly overpassTimeout: UIEventSource<number>;
|
||||||
|
readonly overpassMaxZoom: UIEventSource<number>;
|
||||||
|
readonly osmConnection: OsmConnection
|
||||||
|
readonly currentBounds: UIEventSource<BBox>
|
||||||
|
};
|
||||||
private readonly relationTracker: RelationsTracker
|
private readonly relationTracker: RelationsTracker
|
||||||
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
||||||
|
|
||||||
|
private readonly freshnesses = new Map<string, TileFreshnessCalculator>();
|
||||||
|
|
||||||
|
private readonly oldestAllowedDate: Date = new Date(new Date().getTime() - 60 * 60 * 24 * 30 * 1000);
|
||||||
|
private readonly osmSourceZoomLevel = 14
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
||||||
state: {
|
state: {
|
||||||
|
@ -48,7 +67,7 @@ export default class FeaturePipeline {
|
||||||
readonly locationControl: UIEventSource<Loc>,
|
readonly locationControl: UIEventSource<Loc>,
|
||||||
readonly selectedElement: UIEventSource<any>,
|
readonly selectedElement: UIEventSource<any>,
|
||||||
readonly changes: Changes,
|
readonly changes: Changes,
|
||||||
readonly layoutToUse: LayoutConfig,
|
readonly layoutToUse: LayoutConfig,
|
||||||
readonly leafletMap: any,
|
readonly leafletMap: any,
|
||||||
readonly overpassUrl: UIEventSource<string[]>;
|
readonly overpassUrl: UIEventSource<string[]>;
|
||||||
readonly overpassTimeout: UIEventSource<number>;
|
readonly overpassTimeout: UIEventSource<number>;
|
||||||
|
@ -56,59 +75,15 @@ export default class FeaturePipeline {
|
||||||
readonly osmConnection: OsmConnection
|
readonly osmConnection: OsmConnection
|
||||||
readonly currentBounds: UIEventSource<BBox>
|
readonly currentBounds: UIEventSource<BBox>
|
||||||
}) {
|
}) {
|
||||||
|
this.state = state;
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
|
// milliseconds
|
||||||
/**
|
|
||||||
* Maps tileid onto last download moment
|
|
||||||
*/
|
|
||||||
const tileFreshnesses = new UIEventSource<Map<number, Date>>(new Map<number, Date>())
|
|
||||||
const osmSourceZoomLevel = 14
|
|
||||||
const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12))
|
const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12))
|
||||||
this.relationTracker = new RelationsTracker()
|
this.relationTracker = new RelationsTracker()
|
||||||
|
|
||||||
|
const neededTilesFromOsm = this.getNeededTilesFromOsm()
|
||||||
|
|
||||||
console.log("Tilefreshnesses are", tileFreshnesses.data)
|
|
||||||
const oldestAllowedDate = new Date(new Date().getTime() - (60 * 60 * 24 * 30 * 1000));
|
|
||||||
const neededTilesFromOsm = state.currentBounds.map(bbox => {
|
|
||||||
if (bbox === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const range = bbox.containingTileRange(osmSourceZoomLevel)
|
|
||||||
const tileIndexes = []
|
|
||||||
if (range.total > 100) {
|
|
||||||
// Too much tiles!
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
Tiles.MapRange(range, (x, y) => {
|
|
||||||
const i = Tiles.tile_index(osmSourceZoomLevel, x, y);
|
|
||||||
if (tileFreshnesses.data.get(i) > oldestAllowedDate) {
|
|
||||||
console.debug("Skipping tile", osmSourceZoomLevel, x, y, "as a decently fresh one is available")
|
|
||||||
// The cached tiles contain decently fresh data
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tileIndexes.push(i)
|
|
||||||
})
|
|
||||||
return tileIndexes
|
|
||||||
}, [tileFreshnesses])
|
|
||||||
|
|
||||||
const updater = new OverpassFeatureSource(state,
|
|
||||||
{
|
|
||||||
relationTracker: this.relationTracker,
|
|
||||||
isActive: useOsmApi.map(b => !b),
|
|
||||||
onUpdated: (bbox, freshness) => {
|
|
||||||
// This callback contains metadata of the overpass call
|
|
||||||
const range = bbox.containingTileRange(osmSourceZoomLevel)
|
|
||||||
Tiles.MapRange(range, (x, y) => {
|
|
||||||
tileFreshnesses.data.set(Tiles.tile_index(osmSourceZoomLevel, x, y), freshness)
|
|
||||||
})
|
|
||||||
tileFreshnesses.ping();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.overpassUpdater = updater;
|
|
||||||
this.timeout = updater.timeout
|
|
||||||
|
|
||||||
this.sufficientlyZoomed = state.locationControl.map(location => {
|
this.sufficientlyZoomed = state.locationControl.map(location => {
|
||||||
if (location?.zoom === undefined) {
|
if (location?.zoom === undefined) {
|
||||||
|
@ -119,12 +94,6 @@ export default class FeaturePipeline {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.timeout = updater.timeout
|
|
||||||
|
|
||||||
|
|
||||||
// Register everything in the state' 'AllElements'
|
|
||||||
new RegisteringAllFromFeatureSourceActor(updater)
|
|
||||||
|
|
||||||
|
|
||||||
const perLayerHierarchy = new Map<string, TileHierarchyMerger>()
|
const perLayerHierarchy = new Map<string, TileHierarchyMerger>()
|
||||||
this.perLayerHierarchy = perLayerHierarchy
|
this.perLayerHierarchy = perLayerHierarchy
|
||||||
|
@ -140,40 +109,32 @@ export default class FeaturePipeline {
|
||||||
|
|
||||||
handleFeatureSource(srcFiltered)
|
handleFeatureSource(srcFiltered)
|
||||||
self.somethingLoaded.setData(true)
|
self.somethingLoaded.setData(true)
|
||||||
|
self.freshnesses.get(src.layer.layerDef.id).addTileLoad(src.tileIndex, new Date())
|
||||||
};
|
};
|
||||||
|
|
||||||
function addToHierarchy(src: FeatureSource & Tiled, layerId: string) {
|
|
||||||
perLayerHierarchy.get(layerId).registerTile(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (const filteredLayer of state.filteredLayers.data) {
|
for (const filteredLayer of state.filteredLayers.data) {
|
||||||
const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile))
|
|
||||||
const id = filteredLayer.layerDef.id
|
const id = filteredLayer.layerDef.id
|
||||||
perLayerHierarchy.set(id, hierarchy)
|
|
||||||
const source = filteredLayer.layerDef.source
|
const source = filteredLayer.layerDef.source
|
||||||
|
|
||||||
|
const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile))
|
||||||
|
perLayerHierarchy.set(id, hierarchy)
|
||||||
|
|
||||||
|
this.freshnesses.set(id, new TileFreshnessCalculator())
|
||||||
|
|
||||||
if (source.geojsonSource === undefined) {
|
if (source.geojsonSource === undefined) {
|
||||||
// This is an OSM layer
|
// This is an OSM layer
|
||||||
// We load the cached values and register them
|
// We load the cached values and register them
|
||||||
// Getting data from upstream happens a bit lower
|
// Getting data from upstream happens a bit lower
|
||||||
const localStorage = new TiledFromLocalStorageSource(filteredLayer,
|
new TiledFromLocalStorageSource(filteredLayer,
|
||||||
(src) => {
|
(src) => {
|
||||||
new RegisteringAllFromFeatureSourceActor(src)
|
new RegisteringAllFromFeatureSourceActor(src)
|
||||||
hierarchy.registerTile(src);
|
hierarchy.registerTile(src);
|
||||||
src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
|
src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
|
||||||
}, state)
|
}, state)
|
||||||
|
|
||||||
localStorage.tileFreshness.forEach((value, key) => {
|
TiledFromLocalStorageSource.GetFreshnesses(id).forEach((value, key) => {
|
||||||
if (tileFreshnesses.data.has(key)) {
|
self.freshnesses.get(id).addTileLoad(key, value)
|
||||||
const previous = tileFreshnesses.data.get(key)
|
|
||||||
if (value < previous) {
|
|
||||||
tileFreshnesses.data.set(key, value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tileFreshnesses.data.set(key, value)
|
|
||||||
}
|
|
||||||
tileFreshnesses.ping()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
@ -189,7 +150,7 @@ export default class FeaturePipeline {
|
||||||
dontEnforceMinZoom: true,
|
dontEnforceMinZoom: true,
|
||||||
registerTile: (tile) => {
|
registerTile: (tile) => {
|
||||||
new RegisteringAllFromFeatureSourceActor(tile)
|
new RegisteringAllFromFeatureSourceActor(tile)
|
||||||
addToHierarchy(tile, id)
|
perLayerHierarchy.get(id).registerTile(tile)
|
||||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -198,7 +159,7 @@ export default class FeaturePipeline {
|
||||||
filteredLayer,
|
filteredLayer,
|
||||||
tile => {
|
tile => {
|
||||||
new RegisteringAllFromFeatureSourceActor(tile)
|
new RegisteringAllFromFeatureSourceActor(tile)
|
||||||
addToHierarchy(tile, id)
|
perLayerHierarchy.get(id).registerTile(tile)
|
||||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||||
},
|
},
|
||||||
state
|
state
|
||||||
|
@ -213,14 +174,22 @@ export default class FeaturePipeline {
|
||||||
handleTile: tile => {
|
handleTile: tile => {
|
||||||
new RegisteringAllFromFeatureSourceActor(tile)
|
new RegisteringAllFromFeatureSourceActor(tile)
|
||||||
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
||||||
addToHierarchy(tile, tile.layer.layerDef.id),
|
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
|
||||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||||
|
|
||||||
},
|
},
|
||||||
state: state
|
state: state,
|
||||||
|
markTileVisited: (tileId) =>
|
||||||
|
state.filteredLayers.data.forEach(flayer => {
|
||||||
|
SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const updater = this.initOverpassUpdater(state, useOsmApi)
|
||||||
|
this.overpassUpdater = updater;
|
||||||
|
this.timeout = updater.timeout
|
||||||
|
|
||||||
// Actually load data from the overpass source
|
// Actually load data from the overpass source
|
||||||
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
||||||
(source) => TiledFeatureSource.createHierarchy(source, {
|
(source) => TiledFeatureSource.createHierarchy(source, {
|
||||||
|
@ -232,7 +201,7 @@ export default class FeaturePipeline {
|
||||||
registerTile: (tile) => {
|
registerTile: (tile) => {
|
||||||
// We save the tile data for the given layer to local storage
|
// We save the tile data for the given layer to local storage
|
||||||
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
||||||
addToHierarchy(new RememberingSource(tile), source.layer.layerDef.id);
|
perLayerHierarchy.get(source.layer.layerDef.id).registerTile(new RememberingSource(tile))
|
||||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -247,7 +216,7 @@ export default class FeaturePipeline {
|
||||||
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
||||||
(perLayer) => {
|
(perLayer) => {
|
||||||
// We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this
|
// We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this
|
||||||
addToHierarchy(perLayer, perLayer.layer.layerDef.id)
|
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
||||||
// AT last, we always apply the metatags whenever possible
|
// AT last, we always apply the metatags whenever possible
|
||||||
perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer))
|
perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer))
|
||||||
perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer))
|
perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer))
|
||||||
|
@ -270,6 +239,106 @@ export default class FeaturePipeline {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private freshnessForVisibleLayers(z: number, x: number, y: number): Date {
|
||||||
|
let oldestDate = undefined;
|
||||||
|
for (const flayer of this.state.filteredLayers.data) {
|
||||||
|
if (!flayer.isDisplayed.data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (this.state.locationControl.data.zoom < flayer.layerDef.minzoom) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const freshness = this.freshnesses.get(flayer.layerDef.id).freshnessFor(z, x, y)
|
||||||
|
if (freshness === undefined) {
|
||||||
|
// SOmething is undefined --> we return undefined as we have to download
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (oldestDate === undefined || oldestDate > freshness) {
|
||||||
|
oldestDate = freshness
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oldestDate
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNeededTilesFromOsm(): UIEventSource<number[]> {
|
||||||
|
const self = this
|
||||||
|
return this.state.currentBounds.map(bbox => {
|
||||||
|
if (bbox === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const osmSourceZoomLevel = self.osmSourceZoomLevel
|
||||||
|
const range = bbox.containingTileRange(osmSourceZoomLevel)
|
||||||
|
const tileIndexes = []
|
||||||
|
if (range.total > 100) {
|
||||||
|
// Too much tiles!
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
Tiles.MapRange(range, (x, y) => {
|
||||||
|
const i = Tiles.tile_index(osmSourceZoomLevel, x, y);
|
||||||
|
const oldestDate = self.freshnessForVisibleLayers(osmSourceZoomLevel, x, y)
|
||||||
|
if (oldestDate !== undefined && oldestDate > this.oldestAllowedDate) {
|
||||||
|
console.debug("Skipping tile", osmSourceZoomLevel, x, y, "as a decently fresh one is available")
|
||||||
|
// The cached tiles contain decently fresh data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tileIndexes.push(i)
|
||||||
|
})
|
||||||
|
return tileIndexes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private initOverpassUpdater(state: {
|
||||||
|
layoutToUse: LayoutConfig,
|
||||||
|
currentBounds: UIEventSource<BBox>,
|
||||||
|
locationControl: UIEventSource<Loc>,
|
||||||
|
readonly overpassUrl: UIEventSource<string[]>;
|
||||||
|
readonly overpassTimeout: UIEventSource<number>;
|
||||||
|
readonly overpassMaxZoom: UIEventSource<number>,
|
||||||
|
}, useOsmApi: UIEventSource<boolean>): OverpassFeatureSource {
|
||||||
|
const minzoom = Math.min(...state.layoutToUse.layers.map(layer => layer.minzoom))
|
||||||
|
const allUpToDateAndZoomSufficient = state.currentBounds.map(bbox => {
|
||||||
|
if (bbox === undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let zoom = state.locationControl.data.zoom
|
||||||
|
if (zoom < minzoom) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (zoom > 16) {
|
||||||
|
zoom = 16
|
||||||
|
}
|
||||||
|
if (zoom < 8) {
|
||||||
|
zoom = zoom + 2
|
||||||
|
}
|
||||||
|
const range = bbox.containingTileRange(zoom)
|
||||||
|
const self = this;
|
||||||
|
const allFreshnesses = Tiles.MapRange(range, (x, y) => self.freshnessForVisibleLayers(zoom, x, y))
|
||||||
|
return !allFreshnesses.some(freshness => freshness === undefined || freshness < this.oldestAllowedDate)
|
||||||
|
|
||||||
|
}, [state.locationControl])
|
||||||
|
|
||||||
|
allUpToDateAndZoomSufficient.addCallbackAndRunD(allUpToDate => console.log("All up to data is: ", allUpToDate))
|
||||||
|
const self = this;
|
||||||
|
const updater = new OverpassFeatureSource(state,
|
||||||
|
{
|
||||||
|
relationTracker: this.relationTracker,
|
||||||
|
isActive: useOsmApi.map(b => !b && !allUpToDateAndZoomSufficient.data, [allUpToDateAndZoomSufficient]),
|
||||||
|
onBboxLoaded: ((bbox, date, downloadedLayers) => {
|
||||||
|
Tiles.MapRange(bbox.containingTileRange(self.osmSourceZoomLevel), (x, y) => {
|
||||||
|
downloadedLayers.forEach(layer => {
|
||||||
|
SaveTileToLocalStorageActor.MarkVisited(layer.id, Tiles.tile_index(this.osmSourceZoomLevel, x, y), date)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Register everything in the state' 'AllElements'
|
||||||
|
new RegisteringAllFromFeatureSourceActor(updater)
|
||||||
|
return updater;
|
||||||
|
}
|
||||||
|
|
||||||
private applyMetaTags(src: FeatureSourceForLayer) {
|
private applyMetaTags(src: FeatureSourceForLayer) {
|
||||||
const self = this
|
const self = this
|
||||||
console.debug("Applying metatagging onto ", src.name)
|
console.debug("Applying metatagging onto ", src.name)
|
||||||
|
|
72
Logic/FeatureSource/TileFreshnessCalculator.ts
Normal file
72
Logic/FeatureSource/TileFreshnessCalculator.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {Tiles} from "../../Models/TileRange";
|
||||||
|
|
||||||
|
export default class TileFreshnessCalculator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the freshnesses per tile index
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly freshnesses = new Map<number, Date>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks that some data got loaded for this layer
|
||||||
|
* @param tileId
|
||||||
|
* @param freshness
|
||||||
|
*/
|
||||||
|
public addTileLoad(tileId: number, freshness: Date){
|
||||||
|
const existingFreshness = this.freshnessFor(...Tiles.tile_from_index(tileId))
|
||||||
|
if(existingFreshness >= freshness){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.freshnesses.set(tileId, freshness)
|
||||||
|
|
||||||
|
|
||||||
|
// Do we have freshness for the neighbouring tiles? If so, we can mark the tile above as loaded too!
|
||||||
|
let [z, x, y] = Tiles.tile_from_index(tileId)
|
||||||
|
if(z === 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
x = x - (x % 2) // Make the tiles always even
|
||||||
|
y = y - (y % 2)
|
||||||
|
|
||||||
|
const ul = this.freshnessFor(z, x, y)?.getTime()
|
||||||
|
if(ul === undefined){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ur = this.freshnessFor(z, x + 1, y)?.getTime()
|
||||||
|
if(ur === undefined){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ll = this.freshnessFor(z, x, y + 1)?.getTime()
|
||||||
|
if(ll === undefined){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const lr = this.freshnessFor(z, x + 1, y + 1)?.getTime()
|
||||||
|
if(lr === undefined){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const leastFresh = Math.min(ul, ur, ll, lr)
|
||||||
|
const date = new Date()
|
||||||
|
date.setTime(leastFresh)
|
||||||
|
this.addTileLoad(
|
||||||
|
Tiles.tile_index(z - 1, Math.floor(x / 2), Math.floor(y / 2)),
|
||||||
|
date
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public freshnessFor(z: number, x: number, y:number): Date {
|
||||||
|
if(z < 0){
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const tileId = Tiles.tile_index(z, x, y)
|
||||||
|
if(this.freshnesses.has(tileId)) {
|
||||||
|
return this.freshnesses.get(tileId)
|
||||||
|
}
|
||||||
|
// recurse up
|
||||||
|
return this.freshnessFor(z - 1, Math.floor(x /2), Math.floor(y / 2))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,7 +22,8 @@ export default class OsmFeatureSource {
|
||||||
neededTiles: UIEventSource<number[]>,
|
neededTiles: UIEventSource<number[]>,
|
||||||
state: {
|
state: {
|
||||||
readonly osmConnection: OsmConnection;
|
readonly osmConnection: OsmConnection;
|
||||||
};
|
},
|
||||||
|
markTileVisited?: (tileId: number) => void
|
||||||
};
|
};
|
||||||
private readonly downloadedTiles = new Set<number>()
|
private readonly downloadedTiles = new Set<number>()
|
||||||
|
|
||||||
|
@ -33,7 +34,8 @@ export default class OsmFeatureSource {
|
||||||
state: {
|
state: {
|
||||||
readonly filteredLayers: UIEventSource<FilteredLayer[]>;
|
readonly filteredLayers: UIEventSource<FilteredLayer[]>;
|
||||||
readonly osmConnection: OsmConnection;
|
readonly osmConnection: OsmConnection;
|
||||||
};
|
},
|
||||||
|
markTileVisited?: (tileId: number) => void
|
||||||
}) {
|
}) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this._backend = options.state.osmConnection._oauth_config.url;
|
this._backend = options.state.osmConnection._oauth_config.url;
|
||||||
|
@ -84,13 +86,17 @@ export default class OsmFeatureSource {
|
||||||
flatProperties: true
|
flatProperties: true
|
||||||
});
|
});
|
||||||
console.log("Tile geojson:", z, x, y, "is", geojson)
|
console.log("Tile geojson:", z, x, y, "is", geojson)
|
||||||
|
const index = Tiles.tile_index(z, x, y);
|
||||||
new PerLayerFeatureSourceSplitter(this.filteredLayers,
|
new PerLayerFeatureSourceSplitter(this.filteredLayers,
|
||||||
this.handleTile,
|
this.handleTile,
|
||||||
new StaticFeatureSource(geojson.features, false),
|
new StaticFeatureSource(geojson.features, false),
|
||||||
{
|
{
|
||||||
tileIndex: Tiles.tile_index(z, x, y)
|
tileIndex:index
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if(this.options.markTileVisited){
|
||||||
|
this.options.markTileVisited(index)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Weird error: ", e)
|
console.error("Weird error: ", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import TileHierarchy from "./TileHierarchy";
|
||||||
import {UIEventSource} from "../../UIEventSource";
|
import {UIEventSource} from "../../UIEventSource";
|
||||||
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
|
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
|
||||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||||
import {Utils} from "../../../Utils";
|
|
||||||
import FeatureSourceMerger from "../Sources/FeatureSourceMerger";
|
import FeatureSourceMerger from "../Sources/FeatureSourceMerger";
|
||||||
import {Tiles} from "../../../Models/TileRange";
|
import {Tiles} from "../../../Models/TileRange";
|
||||||
import {BBox} from "../../BBox";
|
import {BBox} from "../../BBox";
|
||||||
|
|
|
@ -3,15 +3,29 @@ import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||||
import {UIEventSource} from "../../UIEventSource";
|
import {UIEventSource} from "../../UIEventSource";
|
||||||
import Loc from "../../../Models/Loc";
|
import Loc from "../../../Models/Loc";
|
||||||
import TileHierarchy from "./TileHierarchy";
|
import TileHierarchy from "./TileHierarchy";
|
||||||
import {Utils} from "../../../Utils";
|
|
||||||
import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor";
|
import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor";
|
||||||
import {Tiles} from "../../../Models/TileRange";
|
import {Tiles} from "../../../Models/TileRange";
|
||||||
import {BBox} from "../../BBox";
|
import {BBox} from "../../BBox";
|
||||||
|
|
||||||
export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
|
export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
|
||||||
public loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
|
public loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
|
||||||
public tileFreshness : Map<number, Date> = new Map<number, Date>()
|
|
||||||
|
public static GetFreshnesses(layerId: string): Map<number, Date> {
|
||||||
|
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layerId + "-"
|
||||||
|
const freshnesses = new Map<number, Date>()
|
||||||
|
for (const key of Object.keys(localStorage)) {
|
||||||
|
if(!(key.startsWith(prefix) && key.endsWith("-time"))){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const index = Number(key.substring(prefix.length, key.length - "-time".length))
|
||||||
|
const time = Number(localStorage.getItem(key))
|
||||||
|
const freshness = new Date()
|
||||||
|
freshness.setTime(time)
|
||||||
|
freshnesses.set(index, freshness)
|
||||||
|
}
|
||||||
|
return freshnesses
|
||||||
|
}
|
||||||
|
|
||||||
constructor(layer: FilteredLayer,
|
constructor(layer: FilteredLayer,
|
||||||
handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
|
handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
|
||||||
state: {
|
state: {
|
||||||
|
@ -33,21 +47,17 @@ public tileFreshness : Map<number, Date> = new Map<number, Date>()
|
||||||
|
|
||||||
console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => Tiles.tile_from_index(i).join("/")).join(", "))
|
console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => Tiles.tile_from_index(i).join("/")).join(", "))
|
||||||
for (const index of indexes) {
|
for (const index of indexes) {
|
||||||
|
|
||||||
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" +index;
|
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" + index;
|
||||||
const version = localStorage.getItem(prefix+"-format")
|
const version = localStorage.getItem(prefix + "-format")
|
||||||
if(version === undefined || version !== SaveTileToLocalStorageActor.formatVersion){
|
if (version === undefined || version !== SaveTileToLocalStorageActor.formatVersion) {
|
||||||
// Invalid version! Remove this tile from local storage
|
// Invalid version! Remove this tile from local storage
|
||||||
localStorage.removeItem(prefix)
|
localStorage.removeItem(prefix)
|
||||||
|
localStorage.removeItem(prefix+"-time")
|
||||||
|
localStorage.removeItem(prefix+"-format")
|
||||||
undefinedTiles.add(index)
|
undefinedTiles.add(index)
|
||||||
console.log("Dropped old format tile", prefix)
|
console.log("Dropped old format tile", prefix)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = Number(localStorage.getItem(prefix+"-time"))
|
|
||||||
const freshness = new Date()
|
|
||||||
freshness.setTime(data)
|
|
||||||
this.tileFreshness.set(index, freshness)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const zLevels = indexes.map(i => i % 100)
|
const zLevels = indexes.map(i => i % 100)
|
||||||
|
@ -91,8 +101,6 @@ public tileFreshness : Map<number, Date> = new Map<number, Date>()
|
||||||
}
|
}
|
||||||
, [layer.isDisplayed, state.leafletMap]).stabilized(50);
|
, [layer.isDisplayed, state.leafletMap]).stabilized(50);
|
||||||
|
|
||||||
neededTiles.addCallbackAndRun(t => console.debug("Tiles to load from localstorage:", t))
|
|
||||||
|
|
||||||
neededTiles.addCallbackAndRunD(neededIndexes => {
|
neededTiles.addCallbackAndRunD(neededIndexes => {
|
||||||
for (const neededIndex of neededIndexes) {
|
for (const neededIndex of neededIndexes) {
|
||||||
// We load the features from localStorage
|
// We load the features from localStorage
|
||||||
|
|
|
@ -97,7 +97,7 @@ export default class SimpleMetaTagger {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const unit of units) {
|
for (const unit of units) {
|
||||||
if(unit === undefined){
|
if (unit === undefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (unit.appliesToKeys === undefined) {
|
if (unit.appliesToKeys === undefined) {
|
||||||
|
@ -108,7 +108,12 @@ export default class SimpleMetaTagger {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const value = feature.properties[key]
|
const value = feature.properties[key]
|
||||||
const [, denomination] = unit.findDenomination(value)
|
const denom = unit.findDenomination(value)
|
||||||
|
if (denom === undefined) {
|
||||||
|
// no valid value found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const [, denomination] = denom;
|
||||||
let canonical = denomination?.canonicalValue(value) ?? undefined;
|
let canonical = denomination?.canonicalValue(value) ?? undefined;
|
||||||
if (canonical === value) {
|
if (canonical === value) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {Utils} from "../Utils";
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
|
|
||||||
public static vNumber = "0.10.0-rc1";
|
public static vNumber = "0.10.0-rc2";
|
||||||
public static ImgurApiKey = '7070e7167f0a25a'
|
public static ImgurApiKey = '7070e7167f0a25a'
|
||||||
public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
|
public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
|
||||||
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
||||||
|
|
|
@ -107,4 +107,5 @@ export class Tiles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@ import UnitsSpec from "./Units.spec";
|
||||||
import RelationSplitHandlerSpec from "./RelationSplitHandler.spec";
|
import RelationSplitHandlerSpec from "./RelationSplitHandler.spec";
|
||||||
import SplitActionSpec from "./SplitAction.spec";
|
import SplitActionSpec from "./SplitAction.spec";
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
|
import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec";
|
||||||
|
|
||||||
|
|
||||||
ScriptUtils.fixUtils()
|
ScriptUtils.fixUtils()
|
||||||
|
@ -21,7 +22,8 @@ const allTests = [
|
||||||
new UtilsSpec(),
|
new UtilsSpec(),
|
||||||
new UnitsSpec(),
|
new UnitsSpec(),
|
||||||
new RelationSplitHandlerSpec(),
|
new RelationSplitHandlerSpec(),
|
||||||
new SplitActionSpec()
|
new SplitActionSpec(),
|
||||||
|
new TileFreshnessCalculatorSpec()
|
||||||
]
|
]
|
||||||
|
|
||||||
Utils.externalDownloadFunction = async (url) => {
|
Utils.externalDownloadFunction = async (url) => {
|
||||||
|
|
31
test/TileFreshnessCalculator.spec.ts
Normal file
31
test/TileFreshnessCalculator.spec.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import T from "./TestHelper";
|
||||||
|
import TileFreshnessCalculator from "../Logic/FeatureSource/TileFreshnessCalculator";
|
||||||
|
import {Tiles} from "../Models/TileRange";
|
||||||
|
import {equal} from "assert";
|
||||||
|
|
||||||
|
export default class TileFreshnessCalculatorSpec extends T {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("TileFreshnessCalculatorSpec", [
|
||||||
|
[
|
||||||
|
"TileFresnessTests",
|
||||||
|
() => {
|
||||||
|
const calc = new TileFreshnessCalculator();
|
||||||
|
// 19/266407/175535
|
||||||
|
const date = new Date()
|
||||||
|
date.setTime(42)
|
||||||
|
calc.addTileLoad(Tiles.tile_index(19, 266406, 175534), date)
|
||||||
|
equal(42, calc.freshnessFor(19, 266406, 175534).getTime())
|
||||||
|
equal(42, calc.freshnessFor(20, 266406 * 2, 175534 * 2 + 1).getTime())
|
||||||
|
equal(undefined, calc.freshnessFor(19, 266406, 175535))
|
||||||
|
equal(undefined, calc.freshnessFor(18, 266406 / 2, 175534 / 2))
|
||||||
|
calc.addTileLoad(Tiles.tile_index(19, 266406, 175534+1), date)
|
||||||
|
calc.addTileLoad(Tiles.tile_index(19, 266406+1, 175534), date)
|
||||||
|
calc.addTileLoad(Tiles.tile_index(19, 266406+1, 175534+1), date)
|
||||||
|
equal(42, calc.freshnessFor(18, 266406 / 2, 175534 / 2).getTime())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue