2021-09-21 02:10:42 +02:00
|
|
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|
|
|
import FilteringFeatureSource from "./Sources/FilteringFeatureSource";
|
|
|
|
import PerLayerFeatureSourceSplitter from "./PerLayerFeatureSourceSplitter";
|
2021-09-28 18:00:44 +02:00
|
|
|
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "./FeatureSource";
|
2021-09-21 02:10:42 +02:00
|
|
|
import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
|
2021-01-15 00:29:07 +01:00
|
|
|
import {UIEventSource} from "../UIEventSource";
|
2021-09-21 02:10:42 +02:00
|
|
|
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
|
2021-07-27 19:39:57 +02:00
|
|
|
import FilteredLayer from "../../Models/FilteredLayer";
|
2021-09-21 02:10:42 +02:00
|
|
|
import MetaTagging from "../MetaTagging";
|
|
|
|
import RememberingSource from "./Sources/RememberingSource";
|
|
|
|
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
|
2021-07-15 20:47:28 +02:00
|
|
|
import {Changes} from "../Osm/Changes";
|
2021-09-21 02:10:42 +02:00
|
|
|
import GeoJsonSource from "./Sources/GeoJsonSource";
|
|
|
|
import Loc from "../../Models/Loc";
|
|
|
|
import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor";
|
|
|
|
import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource";
|
2021-09-22 05:02:09 +02:00
|
|
|
import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor";
|
2021-09-21 02:10:42 +02:00
|
|
|
import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource";
|
|
|
|
import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger";
|
|
|
|
import RelationsTracker from "../Osm/RelationsTracker";
|
2021-09-22 05:02:09 +02:00
|
|
|
import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource";
|
2021-09-22 16:07:56 +02:00
|
|
|
import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator";
|
2021-09-28 17:30:48 +02:00
|
|
|
import {BBox} from "../BBox";
|
|
|
|
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
|
|
|
|
import {OsmConnection} from "../Osm/OsmConnection";
|
|
|
|
import {Tiles} from "../../Models/TileRange";
|
2021-09-30 04:13:23 +02:00
|
|
|
import TileFreshnessCalculator from "./TileFreshnessCalculator";
|
2021-10-28 03:15:36 +02:00
|
|
|
import {ElementStorage} from "../ElementStorage";
|
2021-01-15 00:29:07 +01:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
|
2021-10-18 18:54:36 +02:00
|
|
|
/**
|
|
|
|
* The features pipeline ties together a myriad of various datasources:
|
2021-10-25 20:38:57 +02:00
|
|
|
*
|
2021-10-18 18:54:36 +02:00
|
|
|
* - The Overpass-API
|
|
|
|
* - The OSM-API
|
|
|
|
* - Third-party geojson files, either sliced or directly.
|
2021-10-25 20:38:57 +02:00
|
|
|
*
|
2021-10-18 18:54:36 +02:00
|
|
|
* In order to truly understand this class, please have a look at the following diagram: https://cdn-images-1.medium.com/fit/c/800/618/1*qTK1iCtyJUr4zOyw4IFD7A.jpeg
|
2021-10-25 20:38:57 +02:00
|
|
|
*
|
|
|
|
*
|
2021-10-18 18:54:36 +02:00
|
|
|
*/
|
2021-09-28 17:30:48 +02:00
|
|
|
export default class FeaturePipeline {
|
2021-09-21 02:10:42 +02:00
|
|
|
|
|
|
|
public readonly sufficientlyZoomed: UIEventSource<boolean>;
|
2021-09-29 16:55:05 +02:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
public readonly runningQuery: UIEventSource<boolean>;
|
|
|
|
public readonly timeout: UIEventSource<number>;
|
2021-09-29 16:55:05 +02:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
2021-09-22 05:02:09 +02:00
|
|
|
public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
|
2021-09-21 02:10:42 +02:00
|
|
|
|
|
|
|
private readonly overpassUpdater: OverpassFeatureSource
|
2021-09-30 04:13:23 +02:00
|
|
|
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>
|
|
|
|
};
|
2021-09-21 02:10:42 +02:00
|
|
|
private readonly relationTracker: RelationsTracker
|
|
|
|
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
private readonly freshnesses = new Map<string, TileFreshnessCalculator>();
|
|
|
|
|
2021-10-25 20:38:57 +02:00
|
|
|
private readonly oldestAllowedDate: Date;
|
2021-10-15 18:48:33 +02:00
|
|
|
private readonly osmSourceZoomLevel
|
2021-09-30 04:13:23 +02:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
constructor(
|
2021-09-26 17:36:39 +02:00
|
|
|
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
2021-09-21 02:10:42 +02:00
|
|
|
state: {
|
2021-09-28 17:30:48 +02:00
|
|
|
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
|
|
|
|
readonly locationControl: UIEventSource<Loc>,
|
|
|
|
readonly selectedElement: UIEventSource<any>,
|
|
|
|
readonly changes: Changes,
|
2021-09-30 04:13:23 +02:00
|
|
|
readonly layoutToUse: LayoutConfig,
|
2021-09-28 17:30:48 +02:00
|
|
|
readonly leafletMap: any,
|
2021-09-29 16:55:05 +02:00
|
|
|
readonly overpassUrl: UIEventSource<string[]>;
|
2021-09-21 02:10:42 +02:00
|
|
|
readonly overpassTimeout: UIEventSource<number>;
|
|
|
|
readonly overpassMaxZoom: UIEventSource<number>;
|
2021-09-28 17:30:48 +02:00
|
|
|
readonly osmConnection: OsmConnection
|
2021-10-15 05:20:02 +02:00
|
|
|
readonly currentBounds: UIEventSource<BBox>,
|
2021-10-28 03:15:36 +02:00
|
|
|
readonly osmApiTileSize: UIEventSource<number>,
|
|
|
|
readonly allElements: ElementStorage
|
2021-09-21 02:10:42 +02:00
|
|
|
}) {
|
2021-09-30 04:13:23 +02:00
|
|
|
this.state = state;
|
2021-09-21 02:10:42 +02:00
|
|
|
|
|
|
|
const self = this
|
2021-10-25 20:38:57 +02:00
|
|
|
const expiryInSeconds = Math.min(...state.layoutToUse.layers.map(l => l.maxAgeOfCache))
|
|
|
|
for (const layer of state.layoutToUse.layers) {
|
|
|
|
TiledFromLocalStorageSource.cleanCacheForLayer(layer)
|
|
|
|
}
|
2021-10-25 21:08:44 +02:00
|
|
|
this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds);
|
2021-10-15 18:48:33 +02:00
|
|
|
this.osmSourceZoomLevel = state.osmApiTileSize.data;
|
2021-09-28 17:30:48 +02:00
|
|
|
const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12))
|
|
|
|
this.relationTracker = new RelationsTracker()
|
2021-10-27 03:52:19 +02:00
|
|
|
|
2021-10-25 21:08:44 +02:00
|
|
|
state.changes.allChanges.addCallbackAndRun(allChanges => {
|
2021-10-26 01:14:22 +02:00
|
|
|
allChanges.filter(ch => ch.id < 0 && ch.changes !== undefined)
|
2021-10-25 21:08:44 +02:00
|
|
|
.map(ch => ch.changes)
|
|
|
|
.filter(coor => coor["lat"] !== undefined && coor["lon"] !== undefined)
|
|
|
|
.forEach(coor => {
|
|
|
|
SaveTileToLocalStorageActor.poison(state.layoutToUse.layers.map(l => l.id), coor["lon"], coor["lat"])
|
|
|
|
})
|
|
|
|
})
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-29 16:55:05 +02:00
|
|
|
|
2021-09-28 17:30:48 +02:00
|
|
|
this.sufficientlyZoomed = state.locationControl.map(location => {
|
|
|
|
if (location?.zoom === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-09-28 18:00:44 +02:00
|
|
|
let minzoom = Math.min(...state.layoutToUse.layers.map(layer => layer.minzoom ?? 18));
|
2021-09-28 17:30:48 +02:00
|
|
|
return location.zoom >= minzoom;
|
|
|
|
}
|
|
|
|
);
|
2021-09-29 16:55:05 +02:00
|
|
|
|
2021-10-01 01:34:59 +02:00
|
|
|
const neededTilesFromOsm = this.getNeededTilesFromOsm(this.sufficientlyZoomed)
|
2021-09-21 02:10:42 +02:00
|
|
|
|
|
|
|
const perLayerHierarchy = new Map<string, TileHierarchyMerger>()
|
|
|
|
this.perLayerHierarchy = perLayerHierarchy
|
|
|
|
|
2021-09-26 17:36:39 +02:00
|
|
|
const patchedHandleFeatureSource = function (src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) {
|
2021-09-21 02:10:42 +02:00
|
|
|
// This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
|
|
|
|
const srcFiltered =
|
2021-09-26 17:36:39 +02:00
|
|
|
new FilteringFeatureSource(state, src.tileIndex,
|
2021-09-22 05:02:09 +02:00
|
|
|
new ChangeGeometryApplicator(src, state.changes)
|
2021-09-21 02:10:42 +02:00
|
|
|
)
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
handleFeatureSource(srcFiltered)
|
|
|
|
self.somethingLoaded.setData(true)
|
2021-10-18 18:54:36 +02:00
|
|
|
// We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
|
2021-09-21 02:10:42 +02:00
|
|
|
};
|
|
|
|
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
for (const filteredLayer of state.filteredLayers.data) {
|
|
|
|
const id = filteredLayer.layerDef.id
|
|
|
|
const source = filteredLayer.layerDef.source
|
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile))
|
|
|
|
perLayerHierarchy.set(id, hierarchy)
|
|
|
|
|
|
|
|
this.freshnesses.set(id, new TileFreshnessCalculator())
|
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
if (source.geojsonSource === undefined) {
|
|
|
|
// This is an OSM layer
|
|
|
|
// We load the cached values and register them
|
|
|
|
// Getting data from upstream happens a bit lower
|
2021-09-30 04:13:23 +02:00
|
|
|
new TiledFromLocalStorageSource(filteredLayer,
|
2021-09-21 02:10:42 +02:00
|
|
|
(src) => {
|
|
|
|
new RegisteringAllFromFeatureSourceActor(src)
|
|
|
|
hierarchy.registerTile(src);
|
2021-09-22 05:02:09 +02:00
|
|
|
src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
|
2021-09-21 02:10:42 +02:00
|
|
|
}, state)
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
TiledFromLocalStorageSource.GetFreshnesses(id).forEach((value, key) => {
|
|
|
|
self.freshnesses.get(id).addTileLoad(key, value)
|
2021-09-28 17:30:48 +02:00
|
|
|
})
|
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (source.geojsonZoomLevel === undefined) {
|
|
|
|
// This is a 'load everything at once' geojson layer
|
|
|
|
const src = new GeoJsonSource(filteredLayer)
|
2021-10-02 15:16:41 +02:00
|
|
|
|
|
|
|
if (source.isOsmCacheLayer) {
|
|
|
|
// We split them up into tiles anyway as it is an OSM source
|
|
|
|
TiledFeatureSource.createHierarchy(src, {
|
|
|
|
layer: src.layer,
|
2021-10-11 21:23:14 +02:00
|
|
|
minZoomLevel: this.osmSourceZoomLevel,
|
2021-10-02 15:16:41 +02:00
|
|
|
dontEnforceMinZoom: true,
|
|
|
|
registerTile: (tile) => {
|
|
|
|
new RegisteringAllFromFeatureSourceActor(tile)
|
|
|
|
perLayerHierarchy.get(id).registerTile(tile)
|
|
|
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
|
|
|
}
|
|
|
|
})
|
2021-10-11 22:30:22 +02:00
|
|
|
} else {
|
2021-10-02 15:16:41 +02:00
|
|
|
new RegisteringAllFromFeatureSourceActor(src)
|
|
|
|
perLayerHierarchy.get(id).registerTile(src)
|
|
|
|
src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
|
|
|
|
}
|
2021-09-21 02:10:42 +02:00
|
|
|
} else {
|
|
|
|
new DynamicGeoJsonTileSource(
|
|
|
|
filteredLayer,
|
2021-09-26 17:36:39 +02:00
|
|
|
tile => {
|
2021-09-28 17:30:48 +02:00
|
|
|
new RegisteringAllFromFeatureSourceActor(tile)
|
2021-09-30 04:13:23 +02:00
|
|
|
perLayerHierarchy.get(id).registerTile(tile)
|
2021-09-28 17:30:48 +02:00
|
|
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
|
|
|
},
|
2021-09-21 02:10:42 +02:00
|
|
|
state
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-29 16:55:05 +02:00
|
|
|
const osmFeatureSource = new OsmFeatureSource({
|
2021-09-28 17:30:48 +02:00
|
|
|
isActive: useOsmApi,
|
|
|
|
neededTiles: neededTilesFromOsm,
|
|
|
|
handleTile: tile => {
|
|
|
|
new RegisteringAllFromFeatureSourceActor(tile)
|
2021-10-27 03:52:19 +02:00
|
|
|
if (tile.layer.layerDef.maxAgeOfCache > 0) {
|
|
|
|
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
|
|
|
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
2021-09-28 17:30:48 +02:00
|
|
|
|
|
|
|
},
|
2021-09-30 04:13:23 +02:00
|
|
|
state: state,
|
|
|
|
markTileVisited: (tileId) =>
|
|
|
|
state.filteredLayers.data.forEach(flayer => {
|
2021-10-27 03:52:19 +02:00
|
|
|
if (flayer.layerDef.maxAgeOfCache > 0) {
|
|
|
|
SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date())
|
|
|
|
}
|
2021-10-18 18:54:36 +02:00
|
|
|
self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date())
|
2021-09-30 04:13:23 +02:00
|
|
|
})
|
2021-09-28 17:30:48 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
const updater = this.initOverpassUpdater(state, useOsmApi)
|
|
|
|
this.overpassUpdater = updater;
|
|
|
|
this.timeout = updater.timeout
|
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
// Actually load data from the overpass source
|
|
|
|
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
|
|
|
(source) => TiledFeatureSource.createHierarchy(source, {
|
|
|
|
layer: source.layer,
|
2021-10-11 22:30:22 +02:00
|
|
|
minZoomLevel: source.layer.layerDef.minzoom,
|
2021-09-26 17:36:39 +02:00
|
|
|
dontEnforceMinZoom: true,
|
2021-09-28 18:00:44 +02:00
|
|
|
maxFeatureCount: state.layoutToUse.clustering.minNeededElements,
|
|
|
|
maxZoomLevel: state.layoutToUse.clustering.maxZoom,
|
2021-09-21 02:10:42 +02:00
|
|
|
registerTile: (tile) => {
|
|
|
|
// We save the tile data for the given layer to local storage
|
2021-10-25 20:38:57 +02:00
|
|
|
if (source.layer.layerDef.source.geojsonSource === undefined || source.layer.layerDef.source.isOsmCacheLayer == true) {
|
2021-10-08 15:11:20 +02:00
|
|
|
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
perLayerHierarchy.get(source.layer.layerDef.id).registerTile(new RememberingSource(tile))
|
2021-09-26 17:36:39 +02:00
|
|
|
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
}),
|
2021-09-26 17:36:39 +02:00
|
|
|
updater)
|
2021-09-22 05:02:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
// Also load points/lines that are newly added.
|
|
|
|
const newGeometry = new NewGeometryFromChangesFeatureSource(state.changes)
|
|
|
|
new RegisteringAllFromFeatureSourceActor(newGeometry)
|
|
|
|
// A NewGeometryFromChangesFeatureSource does not split per layer, so we do this next
|
|
|
|
new PerLayerFeatureSourceSplitter(state.filteredLayers,
|
|
|
|
(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
|
2021-09-30 04:13:23 +02:00
|
|
|
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
2021-09-22 05:02:09 +02:00
|
|
|
// AT last, we always apply the metatags whenever possible
|
|
|
|
perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer))
|
2021-09-26 17:36:39 +02:00
|
|
|
perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer))
|
|
|
|
|
2021-09-22 05:02:09 +02:00
|
|
|
},
|
|
|
|
newGeometry
|
|
|
|
)
|
2021-09-21 02:10:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
// Whenever fresh data comes in, we need to update the metatagging
|
2021-10-27 20:19:45 +02:00
|
|
|
self.newDataLoadedSignal.stabilized(250).addCallback(src => {
|
2021-09-21 02:10:42 +02:00
|
|
|
self.updateAllMetaTagging()
|
|
|
|
})
|
|
|
|
|
2021-09-28 17:30:48 +02:00
|
|
|
|
|
|
|
this.runningQuery = updater.runningQuery.map(
|
2021-10-08 15:11:20 +02:00
|
|
|
overpass => {
|
|
|
|
console.log("FeaturePipeline: runningQuery state changed. Overpass", overpass ? "is querying," : "is idle,",
|
2021-10-25 20:38:57 +02:00
|
|
|
"osmFeatureSource is", osmFeatureSource.isRunning ? "is running and needs " + neededTilesFromOsm.data?.length + " tiles (already got " + osmFeatureSource.downloadedTiles.size + " tiles )" : "is idle")
|
2021-10-08 15:11:20 +02:00
|
|
|
return overpass || osmFeatureSource.isRunning.data;
|
|
|
|
}, [osmFeatureSource.isRunning]
|
2021-09-28 17:30:48 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-01-15 00:29:07 +01:00
|
|
|
}
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-10-01 01:34:59 +02:00
|
|
|
private getNeededTilesFromOsm(isSufficientlyZoomed: UIEventSource<boolean>): UIEventSource<number[]> {
|
2021-09-30 04:13:23 +02:00
|
|
|
const self = this
|
|
|
|
return this.state.currentBounds.map(bbox => {
|
|
|
|
if (bbox === undefined) {
|
2021-10-15 18:48:33 +02:00
|
|
|
return []
|
2021-09-30 04:13:23 +02:00
|
|
|
}
|
2021-10-01 01:34:59 +02:00
|
|
|
if (!isSufficientlyZoomed.data) {
|
2021-10-15 18:48:33 +02:00
|
|
|
return [];
|
2021-10-01 01:34:59 +02:00
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
const osmSourceZoomLevel = self.osmSourceZoomLevel
|
|
|
|
const range = bbox.containingTileRange(osmSourceZoomLevel)
|
|
|
|
const tileIndexes = []
|
2021-10-11 22:30:22 +02:00
|
|
|
if (range.total >= 100) {
|
2021-09-30 04:13:23 +02:00
|
|
|
// Too much tiles!
|
2021-10-15 18:48:33 +02:00
|
|
|
return undefined
|
2021-09-30 04:13:23 +02:00
|
|
|
}
|
|
|
|
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
|
2021-10-11 22:30:22 +02:00
|
|
|
return undefined;
|
2021-09-30 04:13:23 +02:00
|
|
|
}
|
|
|
|
tileIndexes.push(i)
|
|
|
|
})
|
|
|
|
return tileIndexes
|
2021-10-15 18:48:33 +02:00
|
|
|
}, [isSufficientlyZoomed])
|
2021-09-30 04:13:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
2021-10-01 04:49:40 +02:00
|
|
|
const overpassIsActive = state.currentBounds.map(bbox => {
|
2021-09-30 04:13:23 +02:00
|
|
|
if (bbox === undefined) {
|
2021-10-01 04:49:40 +02:00
|
|
|
return false
|
2021-10-01 01:34:59 +02:00
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
let zoom = state.locationControl.data.zoom
|
|
|
|
if (zoom < minzoom) {
|
2021-10-18 18:54:36 +02:00
|
|
|
// We are zoomed out over the zoomlevel of any layer
|
2021-10-01 04:49:40 +02:00
|
|
|
return false;
|
2021-09-30 04:13:23 +02:00
|
|
|
}
|
2021-10-02 15:16:41 +02:00
|
|
|
|
2021-09-30 04:13:23 +02:00
|
|
|
const range = bbox.containingTileRange(zoom)
|
2021-10-11 22:30:22 +02:00
|
|
|
if (range.total >= 5000) {
|
2021-10-18 18:54:36 +02:00
|
|
|
// Let's assume we don't have so much data cached
|
|
|
|
return true
|
2021-10-01 04:49:40 +02:00
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
const self = this;
|
|
|
|
const allFreshnesses = Tiles.MapRange(range, (x, y) => self.freshnessForVisibleLayers(zoom, x, y))
|
2021-10-01 04:49:40 +02:00
|
|
|
return allFreshnesses.some(freshness => freshness === undefined || freshness < this.oldestAllowedDate)
|
2021-09-30 04:13:23 +02:00
|
|
|
}, [state.locationControl])
|
|
|
|
|
|
|
|
const self = this;
|
|
|
|
const updater = new OverpassFeatureSource(state,
|
|
|
|
{
|
2021-10-11 22:30:22 +02:00
|
|
|
padToTiles: state.locationControl.map(l => Math.min(15, l.zoom + 1)),
|
2021-09-30 04:13:23 +02:00
|
|
|
relationTracker: this.relationTracker,
|
2021-10-01 04:49:40 +02:00
|
|
|
isActive: useOsmApi.map(b => !b && overpassIsActive.data, [overpassIsActive]),
|
2021-10-11 22:30:22 +02:00
|
|
|
onBboxLoaded: (bbox, date, downloadedLayers, paddedToZoomLevel) => {
|
|
|
|
Tiles.MapRange(bbox.containingTileRange(paddedToZoomLevel), (x, y) => {
|
2021-10-25 20:38:57 +02:00
|
|
|
const tileIndex = Tiles.tile_index(paddedToZoomLevel, x, y)
|
2021-09-30 04:13:23 +02:00
|
|
|
downloadedLayers.forEach(layer => {
|
2021-10-11 22:30:22 +02:00
|
|
|
self.freshnesses.get(layer.id).addTileLoad(tileIndex, date)
|
|
|
|
SaveTileToLocalStorageActor.MarkVisited(layer.id, tileIndex, date)
|
2021-09-30 04:13:23 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-10-11 22:30:22 +02:00
|
|
|
}
|
2021-09-30 04:13:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Register everything in the state' 'AllElements'
|
|
|
|
new RegisteringAllFromFeatureSourceActor(updater)
|
|
|
|
return updater;
|
|
|
|
}
|
|
|
|
|
2021-09-28 17:30:48 +02:00
|
|
|
private applyMetaTags(src: FeatureSourceForLayer) {
|
2021-09-22 05:02:09 +02:00
|
|
|
const self = this
|
2021-09-27 14:45:48 +02:00
|
|
|
window.setTimeout(
|
|
|
|
() => {
|
2021-10-04 00:18:08 +02:00
|
|
|
const layerDef = src.layer.layerDef;
|
2021-10-27 20:19:45 +02:00
|
|
|
const somethingChanged = MetaTagging.addMetatags(
|
2021-09-27 14:45:48 +02:00
|
|
|
src.features.data,
|
|
|
|
{
|
|
|
|
memberships: this.relationTracker,
|
|
|
|
getFeaturesWithin: (layerId, bbox: BBox) => self.GetFeaturesWithin(layerId, bbox)
|
|
|
|
},
|
2021-10-04 00:18:08 +02:00
|
|
|
layerDef,
|
2021-09-27 14:45:48 +02:00
|
|
|
{
|
|
|
|
includeDates: true,
|
|
|
|
// We assume that the non-dated metatags are already set by the cache generator
|
2021-10-04 00:18:08 +02:00
|
|
|
includeNonDates: layerDef.source.geojsonSource === undefined || !layerDef.source.isOsmCacheLayer
|
2021-09-27 14:45:48 +02:00
|
|
|
}
|
|
|
|
)
|
2021-09-22 05:02:09 +02:00
|
|
|
},
|
2021-09-27 14:45:48 +02:00
|
|
|
15
|
2021-09-22 05:02:09 +02:00
|
|
|
)
|
2021-09-28 17:30:48 +02:00
|
|
|
|
2021-09-22 05:02:09 +02:00
|
|
|
}
|
2021-01-15 00:29:07 +01:00
|
|
|
|
2021-09-21 02:10:42 +02:00
|
|
|
private updateAllMetaTagging() {
|
|
|
|
const self = this;
|
2021-10-27 20:19:45 +02:00
|
|
|
console.debug("Updating the meta tagging of all tiles as new data got loaded")
|
2021-09-21 02:10:42 +02:00
|
|
|
this.perLayerHierarchy.forEach(hierarchy => {
|
2021-10-27 20:19:45 +02:00
|
|
|
hierarchy.loadedTiles.forEach(tile => {
|
|
|
|
self.applyMetaTags(tile)
|
2021-09-21 02:10:42 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
|
|
|
public GetAllFeaturesWithin(bbox: BBox): any[][] {
|
2021-09-21 02:10:42 +02:00
|
|
|
const self = this
|
|
|
|
const tiles = []
|
|
|
|
Array.from(this.perLayerHierarchy.keys())
|
|
|
|
.forEach(key => tiles.push(...self.GetFeaturesWithin(key, bbox)))
|
|
|
|
return tiles;
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
|
|
|
public GetFeaturesWithin(layerId: string, bbox: BBox): any[][] {
|
2021-10-25 20:38:57 +02:00
|
|
|
if (layerId === "*") {
|
2021-10-12 02:12:45 +02:00
|
|
|
return this.GetAllFeaturesWithin(bbox)
|
|
|
|
}
|
2021-09-21 02:10:42 +02:00
|
|
|
const requestedHierarchy = this.perLayerHierarchy.get(layerId)
|
|
|
|
if (requestedHierarchy === undefined) {
|
2021-09-22 05:02:09 +02:00
|
|
|
console.warn("Layer ", layerId, "is not defined. Try one of ", Array.from(this.perLayerHierarchy.keys()))
|
2021-09-21 02:10:42 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return TileHierarchyTools.getTiles(requestedHierarchy, bbox)
|
|
|
|
.filter(featureSource => featureSource.features?.data !== undefined)
|
|
|
|
.map(featureSource => featureSource.features.data.map(fs => fs.feature))
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
|
|
|
public GetTilesPerLayerWithin(bbox: BBox, handleTile: (tile: FeatureSourceForLayer & Tiled) => void) {
|
|
|
|
Array.from(this.perLayerHierarchy.values()).forEach(hierarchy => {
|
2021-09-21 02:10:42 +02:00
|
|
|
TileHierarchyTools.getTiles(hierarchy, bbox).forEach(handleTile)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-15 00:29:07 +01:00
|
|
|
}
|