Performance optimazations
This commit is contained in:
parent
632e7e9f9a
commit
d2b245ab54
15 changed files with 321 additions and 214 deletions
|
@ -117,7 +117,6 @@ export default class SelectedElementTagsUpdater {
|
|||
|
||||
const localValue = currentTags[key]
|
||||
if (localValue !== osmValue) {
|
||||
console.log("Local value for ", key, ":", localValue, "upstream", osmValue)
|
||||
somethingChanged = true;
|
||||
currentTags[key] = osmValue
|
||||
}
|
||||
|
|
|
@ -48,12 +48,12 @@ export class BBox {
|
|||
}
|
||||
return feature.bbox;
|
||||
}
|
||||
|
||||
static bboxAroundAll(bboxes: BBox[]): BBox{
|
||||
|
||||
static bboxAroundAll(bboxes: BBox[]): BBox {
|
||||
let maxLat: number = -90;
|
||||
let maxLon: number= -180;
|
||||
let minLat: number= 80;
|
||||
let minLon: number= 180;
|
||||
let maxLon: number = -180;
|
||||
let minLat: number = 80;
|
||||
let minLon: number = 180;
|
||||
|
||||
for (const bbox of bboxes) {
|
||||
maxLat = Math.max(maxLat, bbox.maxLat)
|
||||
|
@ -61,7 +61,7 @@ export class BBox {
|
|||
minLat = Math.min(minLat, bbox.minLat)
|
||||
minLon = Math.min(minLon, bbox.minLon)
|
||||
}
|
||||
return new BBox([[maxLon, maxLat],[minLon,minLat]])
|
||||
return new BBox([[maxLon, maxLat], [minLon, minLat]])
|
||||
}
|
||||
|
||||
static fromTile(z: number, x: number, y: number): BBox {
|
||||
|
@ -75,6 +75,14 @@ export class BBox {
|
|||
return BBox.fromTile(...Tiles.tile_from_index(i))
|
||||
}
|
||||
|
||||
public unionWith(other: BBox) {
|
||||
return new BBox([[
|
||||
Math.max(this.maxLon, other.maxLon),
|
||||
Math.max(this.maxLat, other.maxLat)],
|
||||
[Math.min(this.minLon, other.minLon),
|
||||
Math.min(this.minLat, other.minLat)]])
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tilerange which fully contains this bbox (thus might be a bit larger)
|
||||
* @param zoomlevel
|
||||
|
|
112
Logic/FeatureSource/Actors/MetaTagRecalculator.ts
Normal file
112
Logic/FeatureSource/Actors/MetaTagRecalculator.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import MetaTagging from "../../MetaTagging";
|
||||
import {ElementStorage} from "../../ElementStorage";
|
||||
import {ExtraFuncParams} from "../../ExtraFunctions";
|
||||
import FeaturePipeline from "../FeaturePipeline";
|
||||
import {BBox} from "../../BBox";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
|
||||
/****
|
||||
* Concerned with the logic of updating the right layer at the right time
|
||||
*/
|
||||
class MetatagUpdater {
|
||||
public readonly neededLayerBboxes = new Map<string /*layerId*/, BBox>()
|
||||
private source: FeatureSourceForLayer & Tiled;
|
||||
private readonly params: ExtraFuncParams
|
||||
private state: { allElements?: ElementStorage };
|
||||
|
||||
private readonly isDirty = new UIEventSource(false)
|
||||
|
||||
constructor(source: FeatureSourceForLayer & Tiled, state: { allElements?: ElementStorage }, featurePipeline: FeaturePipeline) {
|
||||
this.state = state;
|
||||
this.source = source;
|
||||
const self = this;
|
||||
this.params = {
|
||||
getFeatureById(id) {
|
||||
return state.allElements.ContainingFeatures.get(id)
|
||||
},
|
||||
getFeaturesWithin(layerId, bbox) {
|
||||
// We keep track of the BBOX that this source needs
|
||||
let oldBbox: BBox = self.neededLayerBboxes.get(layerId)
|
||||
if (oldBbox === undefined) {
|
||||
self.neededLayerBboxes.set(layerId, bbox);
|
||||
} else if (!bbox.isContainedIn(oldBbox)) {
|
||||
self.neededLayerBboxes.set(layerId,oldBbox.unionWith(bbox))
|
||||
}
|
||||
return featurePipeline.GetFeaturesWithin(layerId, bbox)
|
||||
},
|
||||
memberships: featurePipeline.relationTracker
|
||||
}
|
||||
this.isDirty.stabilized(100).addCallback(dirty => {
|
||||
if(dirty){
|
||||
self.updateMetaTags()
|
||||
}
|
||||
})
|
||||
this.source.features.addCallbackAndRunD(_ => self.isDirty.setData(true))
|
||||
|
||||
}
|
||||
|
||||
public requestUpdate(){
|
||||
this.isDirty.setData(true)
|
||||
}
|
||||
|
||||
private updateMetaTags() {
|
||||
const features = this.source.features.data
|
||||
|
||||
if (features.length === 0) {
|
||||
this.isDirty.setData(false)
|
||||
return
|
||||
}
|
||||
MetaTagging.addMetatags(
|
||||
features,
|
||||
this.params,
|
||||
this.source.layer.layerDef,
|
||||
this.state)
|
||||
this.isDirty.setData(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class MetaTagRecalculator {
|
||||
private _state: {
|
||||
allElements?: ElementStorage
|
||||
};
|
||||
private _featurePipeline: FeaturePipeline;
|
||||
private readonly _alreadyRegistered: Set<FeatureSourceForLayer & Tiled> = new Set<FeatureSourceForLayer & Tiled>()
|
||||
private readonly _notifiers : MetatagUpdater[] = []
|
||||
/**
|
||||
* The meta tag recalculator receives tiles of layers.
|
||||
* It keeps track of which sources have had their share calculated, and which should be re-updated if some other data is loaded
|
||||
*/
|
||||
constructor(state: { allElements?: ElementStorage }, featurePipeline: FeaturePipeline) {
|
||||
this._featurePipeline = featurePipeline;
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
public registerSource(source: FeatureSourceForLayer & Tiled, recalculateOnEveryChange = false) {
|
||||
if (source === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this._alreadyRegistered.has(source)) {
|
||||
return;
|
||||
}
|
||||
this._alreadyRegistered.add(source)
|
||||
this._notifiers.push(new MetatagUpdater(source,this._state,this._featurePipeline))
|
||||
const self = this;
|
||||
source.features.addCallbackAndRunD(_ => {
|
||||
const layerName = source.layer.layerDef.id
|
||||
for (const updater of self._notifiers ) {
|
||||
const neededBbox = updater.neededLayerBboxes.get(layerName)
|
||||
if(neededBbox == undefined){
|
||||
continue
|
||||
}
|
||||
if(source.bbox === undefined || neededBbox.overlapsWith(source.bbox)){
|
||||
updater.requestUpdate()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,6 @@ import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from
|
|||
import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
|
||||
import MetaTagging from "../MetaTagging";
|
||||
import RememberingSource from "./Sources/RememberingSource";
|
||||
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
|
||||
import GeoJsonSource from "./Sources/GeoJsonSource";
|
||||
|
@ -49,7 +48,7 @@ export default class FeaturePipeline {
|
|||
|
||||
private readonly overpassUpdater: OverpassFeatureSource
|
||||
private state: MapState;
|
||||
private readonly relationTracker: RelationsTracker
|
||||
public readonly relationTracker: RelationsTracker
|
||||
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
||||
|
||||
/**
|
||||
|
@ -63,9 +62,7 @@ export default class FeaturePipeline {
|
|||
private readonly osmSourceZoomLevel
|
||||
|
||||
private readonly localStorageSavers = new Map<string, SaveTileToLocalStorageActor>()
|
||||
private readonly metataggingRecalculated = new UIEventSource<void>(undefined)
|
||||
private readonly requestMetataggingRecalculation = new UIEventSource<Date>(undefined)
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of all raw OSM-nodes.
|
||||
* Only initialized if 'type_node' is defined as layer
|
||||
|
@ -74,7 +71,12 @@ export default class FeaturePipeline {
|
|||
|
||||
constructor(
|
||||
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
||||
state: MapState) {
|
||||
state: MapState,
|
||||
options? : {
|
||||
/*Used for metatagging - will receive all the sources with changeGeometry applied but without filtering*/
|
||||
handleRawFeatureSource: (source: FeatureSourceForLayer) => void
|
||||
}
|
||||
) {
|
||||
this.state = state;
|
||||
|
||||
const self = this
|
||||
|
@ -102,10 +104,6 @@ export default class FeaturePipeline {
|
|||
return location.zoom >= minzoom;
|
||||
}
|
||||
);
|
||||
|
||||
this.requestMetataggingRecalculation.stabilized(500).addCallbackAndRunD(_ => {
|
||||
self.updateAllMetaTagging("Request stabilized")
|
||||
})
|
||||
|
||||
const neededTilesFromOsm = this.getNeededTilesFromOsm(this.sufficientlyZoomed)
|
||||
|
||||
|
@ -115,13 +113,13 @@ export default class FeaturePipeline {
|
|||
// Given a tile, wraps it and passes it on to render (handled by 'handleFeatureSource'
|
||||
function patchedHandleFeatureSource(src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) {
|
||||
// This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
|
||||
const srcFiltered =
|
||||
new FilteringFeatureSource(state, src.tileIndex,
|
||||
new ChangeGeometryApplicator(src, state.changes),
|
||||
self.metataggingRecalculated
|
||||
)
|
||||
const withChanges = new ChangeGeometryApplicator(src, state.changes);
|
||||
const srcFiltered = new FilteringFeatureSource(state, src.tileIndex,withChanges)
|
||||
|
||||
handleFeatureSource(srcFiltered)
|
||||
if(options?.handleRawFeatureSource){
|
||||
options.handleRawFeatureSource(withChanges)
|
||||
}
|
||||
self.somethingLoaded.setData(true)
|
||||
// We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
|
||||
}
|
||||
|
@ -178,11 +176,6 @@ export default class FeaturePipeline {
|
|||
|
||||
if (id === "current_view") {
|
||||
handlePriviligedFeatureSource(state.currentView)
|
||||
state.currentView.features.map(ffs => ffs[0]?.feature?.properties?.id).withEqualityStabilized((x,y) => x === y)
|
||||
.addCallbackAndRunD(_ => {
|
||||
self.applyMetaTags(state.currentView, <any>this.state, `currentview changed`)
|
||||
}
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -324,7 +317,6 @@ export default class FeaturePipeline {
|
|||
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
||||
// AT last, we always apply the metatags whenever possible
|
||||
perLayer.features.addCallbackAndRunD(feats => {
|
||||
console.log("New feature for layer ", perLayer.layer.layerDef.id, ":", feats)
|
||||
self.onNewDataLoaded(perLayer);
|
||||
})
|
||||
|
||||
|
@ -335,13 +327,6 @@ export default class FeaturePipeline {
|
|||
}}
|
||||
)
|
||||
|
||||
|
||||
// Whenever fresh data comes in, we need to update the metatagging
|
||||
self.newDataLoadedSignal.stabilized(250).addCallback(src => {
|
||||
self.updateAllMetaTagging(`New data loaded by ${src.name} (and stabilized)`)
|
||||
})
|
||||
|
||||
|
||||
this.runningQuery = updater.runningQuery.map(
|
||||
overpass => {
|
||||
console.log("FeaturePipeline: runningQuery state changed: Overpass", overpass ? "is querying," : "is idle,",
|
||||
|
@ -354,7 +339,6 @@ export default class FeaturePipeline {
|
|||
|
||||
private onNewDataLoaded(src: FeatureSource){
|
||||
this.newDataLoadedSignal.setData(src)
|
||||
this.requestMetataggingRecalculation.setData(new Date())
|
||||
}
|
||||
|
||||
public GetAllFeaturesWithin(bbox: BBox): any[][] {
|
||||
|
@ -501,44 +485,4 @@ export default class FeaturePipeline {
|
|||
return updater;
|
||||
}
|
||||
|
||||
private applyMetaTags(src: FeatureSourceForLayer, state: any, reason: string) {
|
||||
const self = this
|
||||
if(src === undefined){
|
||||
throw "Src is undefined"
|
||||
}
|
||||
const layerDef = src.layer.layerDef;
|
||||
console.debug(`Applying metatags onto ${src.name} due to ${reason} which has ${src.features.data?.length} features`)
|
||||
if(src.features.data.length == 0){
|
||||
return
|
||||
}
|
||||
MetaTagging.addMetatags(
|
||||
src.features.data,
|
||||
{
|
||||
memberships: this.relationTracker,
|
||||
getFeaturesWithin: (layerId, bbox: BBox) => self.GetFeaturesWithin(layerId, bbox),
|
||||
getFeatureById: (id: string) => self.state.allElements.ContainingFeatures.get(id)
|
||||
},
|
||||
layerDef,
|
||||
state,
|
||||
{
|
||||
includeDates: true,
|
||||
// We assume that the non-dated metatags are already set by the cache generator
|
||||
includeNonDates: layerDef.source.geojsonSource === undefined || !layerDef.source.isOsmCacheLayer
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
public updateAllMetaTagging(reason: string) {
|
||||
const self = this;
|
||||
this.perLayerHierarchy.forEach(hierarchy => {
|
||||
hierarchy.loadedTiles.forEach(tile => {
|
||||
self.applyMetaTags(tile, <any> this.state, `${reason} (tile ${tile.tileIndex})`)
|
||||
})
|
||||
})
|
||||
self.metataggingRecalculated.ping()
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -59,7 +59,7 @@ export class GeoOperations {
|
|||
const coor = feature.geometry.coordinates;
|
||||
for (const otherFeature of otherFeatures) {
|
||||
|
||||
if (feature.id !== undefined && feature.id === otherFeature.id) {
|
||||
if (feature.properties.id !== undefined && feature.properties.id === otherFeature.properties.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class GeoOperations {
|
|||
|
||||
for (const otherFeature of otherFeatures) {
|
||||
|
||||
if (feature.id !== undefined && feature.id === otherFeature.id) {
|
||||
if (feature.properties.id !== undefined && feature.properties.id === otherFeature.properties.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ export class GeoOperations {
|
|||
|
||||
for (const otherFeature of otherFeatures) {
|
||||
|
||||
if (feature.id === otherFeature.id) {
|
||||
if (feature.properties.id !== undefined && feature.properties.id === otherFeature.properties.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger";
|
||||
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import {ElementStorage} from "./ElementStorage";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -23,7 +24,7 @@ export default class MetaTagging {
|
|||
public static addMetatags(features: { feature: any; freshness: Date }[],
|
||||
params: ExtraFuncParams,
|
||||
layer: LayerConfig,
|
||||
state,
|
||||
state?: {allElements?: ElementStorage},
|
||||
options?: {
|
||||
includeDates?: true | boolean,
|
||||
includeNonDates?: true | boolean
|
||||
|
@ -35,11 +36,11 @@ export default class MetaTagging {
|
|||
const metatagsToApply: SimpleMetaTagger[] = []
|
||||
for (const metatag of SimpleMetaTaggers.metatags) {
|
||||
if (metatag.includesDates) {
|
||||
if (options.includeDates ?? true) {
|
||||
if (options?.includeDates ?? true) {
|
||||
metatagsToApply.push(metatag)
|
||||
}
|
||||
} else {
|
||||
if (options.includeNonDates ?? true) {
|
||||
if (options?.includeNonDates ?? true) {
|
||||
metatagsToApply.push(metatag)
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +104,8 @@ export default class MetaTagging {
|
|||
}
|
||||
return atLeastOneFeatureChanged
|
||||
}
|
||||
public static createFunctionsForFeature(layerId: string, calculatedTags: [string, string, boolean][]): ((feature: any) => void)[] {
|
||||
|
||||
private static createFunctionsForFeature(layerId: string, calculatedTags: [string, string, boolean][]): ((feature: any) => void)[] {
|
||||
const functions: ((feature: any) => any)[] = [];
|
||||
for (const entry of calculatedTags) {
|
||||
const key = entry[0]
|
||||
|
|
|
@ -9,7 +9,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
|||
import {CountryCoder} from "latlon2country"
|
||||
|
||||
|
||||
export class SimpleMetaTagger {
|
||||
export class SimpleMetaTagger {
|
||||
public readonly keys: string[];
|
||||
public readonly doc: string;
|
||||
public readonly isLazy: boolean;
|
||||
|
@ -42,9 +42,9 @@ export class SimpleMetaTagger {
|
|||
export class CountryTagger extends SimpleMetaTagger {
|
||||
private static readonly coder = new CountryCoder("https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country", Utils.downloadJson);
|
||||
public runningTasks: Set<any>;
|
||||
|
||||
|
||||
constructor() {
|
||||
const runningTasks= new Set<any>();
|
||||
const runningTasks = new Set<any>();
|
||||
super
|
||||
(
|
||||
{
|
||||
|
@ -77,20 +77,12 @@ export class CountryTagger extends SimpleMetaTagger {
|
|||
return false;
|
||||
})
|
||||
)
|
||||
this.runningTasks = runningTasks;
|
||||
this.runningTasks = runningTasks;
|
||||
}
|
||||
}
|
||||
|
||||
export default class SimpleMetaTaggers {
|
||||
|
||||
private static readonly cardinalDirections = {
|
||||
N: 0, NNE: 22.5, NE: 45, ENE: 67.5,
|
||||
E: 90, ESE: 112.5, SE: 135, SSE: 157.5,
|
||||
S: 180, SSW: 202.5, SW: 225, WSW: 247.5,
|
||||
W: 270, WNW: 292.5, NW: 315, NNW: 337.5
|
||||
}
|
||||
|
||||
|
||||
public static readonly objectMetaInfo = new SimpleMetaTagger(
|
||||
{
|
||||
keys: ["_last_edit:contributor",
|
||||
|
@ -121,7 +113,25 @@ export default class SimpleMetaTaggers {
|
|||
return true;
|
||||
}
|
||||
)
|
||||
public static country = new CountryTagger()
|
||||
public static geometryType = new SimpleMetaTagger(
|
||||
{
|
||||
keys: ["_geometry:type"],
|
||||
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
|
||||
},
|
||||
(feature, _) => {
|
||||
const changed = feature.properties["_geometry:type"] === feature.geometry.type;
|
||||
feature.properties["_geometry:type"] = feature.geometry.type;
|
||||
return changed
|
||||
}
|
||||
)
|
||||
|
||||
private static readonly cardinalDirections = {
|
||||
N: 0, NNE: 22.5, NE: 45, ENE: 67.5,
|
||||
E: 90, ESE: 112.5, SE: 135, SSE: 157.5,
|
||||
S: 180, SSW: 202.5, SW: 225, WSW: 247.5,
|
||||
W: 270, WNW: 292.5, NW: 315, NNW: 337.5
|
||||
}
|
||||
private static latlon = new SimpleMetaTagger({
|
||||
keys: ["_lat", "_lon"],
|
||||
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
|
||||
|
@ -201,7 +211,6 @@ export default class SimpleMetaTaggers {
|
|||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
private static canonicalize = new SimpleMetaTagger(
|
||||
{
|
||||
doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`)",
|
||||
|
@ -254,7 +263,6 @@ export default class SimpleMetaTaggers {
|
|||
return rewritten
|
||||
})
|
||||
)
|
||||
|
||||
private static lngth = new SimpleMetaTagger(
|
||||
{
|
||||
keys: ["_length", "_length:km"],
|
||||
|
@ -269,7 +277,6 @@ export default class SimpleMetaTaggers {
|
|||
return true;
|
||||
})
|
||||
)
|
||||
public static country = new CountryTagger()
|
||||
private static isOpen = new SimpleMetaTagger(
|
||||
{
|
||||
keys: ["_isOpen"],
|
||||
|
@ -277,23 +284,33 @@ export default class SimpleMetaTaggers {
|
|||
includesDates: true,
|
||||
isLazy: true
|
||||
},
|
||||
((feature, _, __ ,state) => {
|
||||
((feature, _, __, state) => {
|
||||
if (Utils.runningFromConsole) {
|
||||
// We are running from console, thus probably creating a cache
|
||||
// isOpen is irrelevant
|
||||
return false
|
||||
}
|
||||
if(feature.properties.opening_hours === "24/7"){
|
||||
feature.properties._isOpen = "yes"
|
||||
return true;
|
||||
}
|
||||
|
||||
Object.defineProperty(feature.properties, "_isOpen", {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
get: () => {
|
||||
if(feature.properties.id === "node/7464543832"){
|
||||
console.log("Getting _isOpen for ", feature.properties.i)
|
||||
}
|
||||
delete feature.properties._isOpen
|
||||
feature.properties._isOpen = undefined
|
||||
const tagsSource = state.allElements.getEventSourceById(feature.properties.id);
|
||||
tagsSource
|
||||
.addCallbackAndRunD(tags => {
|
||||
if (tags.opening_hours === undefined || tags._country === undefined) {
|
||||
tagsSource.addCallbackAndRunD(tags => {
|
||||
// Install a listener to the tags...
|
||||
if (tags.opening_hours === undefined){
|
||||
return;
|
||||
}
|
||||
if(tags._country === undefined) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -305,18 +322,20 @@ export default class SimpleMetaTaggers {
|
|||
country_code: tags._country.toLowerCase()
|
||||
}
|
||||
}, {tag_key: "opening_hours"});
|
||||
// AUtomatically triggered on the next change
|
||||
|
||||
// AUtomatically triggered on the next change (and a bit below)
|
||||
const updateTags = () => {
|
||||
const oldValueIsOpen = tags["_isOpen"];
|
||||
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
|
||||
|
||||
if (oldNextChange > (new Date()).getTime() &&
|
||||
tags["_isOpen:oldvalue"] === tags["opening_hours"]
|
||||
tags["_isOpen:oldvalue"] === tags["opening_hours"] // Check that changes have to be made
|
||||
&& tags["_isOpen"] !== undefined) {
|
||||
// Already calculated and should not yet be triggered
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recalculate!
|
||||
tags["_isOpen"] = oh.getState() ? "yes" : "no";
|
||||
const comment = oh.getComment();
|
||||
if (comment) {
|
||||
|
@ -380,8 +399,6 @@ export default class SimpleMetaTaggers {
|
|||
return true;
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
private static currentTime = new SimpleMetaTagger(
|
||||
{
|
||||
keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"],
|
||||
|
@ -410,20 +427,6 @@ export default class SimpleMetaTaggers {
|
|||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
public static geometryType = new SimpleMetaTagger(
|
||||
{
|
||||
keys:["_geometry:type"],
|
||||
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
|
||||
},
|
||||
(feature, _) => {
|
||||
const changed = feature.properties["_geometry:type"] === feature.geometry.type;
|
||||
feature.properties["_geometry:type"] = feature.geometry.type;
|
||||
return changed
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
public static metatags: SimpleMetaTagger[] = [
|
||||
SimpleMetaTaggers.latlon,
|
||||
SimpleMetaTaggers.layerInfo,
|
||||
|
@ -439,11 +442,9 @@ export default class SimpleMetaTaggers {
|
|||
SimpleMetaTaggers.geometryType
|
||||
|
||||
];
|
||||
|
||||
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy)
|
||||
.map(tagger => tagger.keys));
|
||||
|
||||
|
||||
/**
|
||||
* Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme.
|
||||
* These changes are performed in-place.
|
||||
|
|
|
@ -10,6 +10,8 @@ import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler";
|
|||
import Hash from "../Web/Hash";
|
||||
import {BBox} from "../BBox";
|
||||
import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource";
|
||||
import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator";
|
||||
|
||||
export default class FeaturePipelineState extends MapState {
|
||||
|
||||
|
@ -18,7 +20,7 @@ export default class FeaturePipelineState extends MapState {
|
|||
*/
|
||||
public readonly featurePipeline: FeaturePipeline;
|
||||
private readonly featureAggregator: TileHierarchyAggregator;
|
||||
|
||||
private readonly metatagRecalculator : MetaTagRecalculator
|
||||
constructor(layoutToUse: LayoutConfig) {
|
||||
super(layoutToUse);
|
||||
|
||||
|
@ -26,81 +28,106 @@ export default class FeaturePipelineState extends MapState {
|
|||
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this);
|
||||
const clusterCounter = this.featureAggregator
|
||||
const self = this;
|
||||
this.featurePipeline = new FeaturePipeline(
|
||||
source => {
|
||||
|
||||
clusterCounter.addTile(source)
|
||||
/**
|
||||
* We are a bit in a bind:
|
||||
* There is the featurePipeline, which creates some sources during construction
|
||||
* THere is the metatagger, which needs to have these sources registered AND which takes a FeaturePipeline as argument
|
||||
*
|
||||
* This is a bit of a catch-22 (except that it isn't)
|
||||
* The sources that are registered in the constructor are saved into 'registeredSources' temporary
|
||||
*
|
||||
*/
|
||||
const sourcesToRegister = []
|
||||
|
||||
function registerRaw(source: FeatureSourceForLayer & Tiled){
|
||||
if(self.metatagRecalculator === undefined){
|
||||
sourcesToRegister.push(source)
|
||||
}else{
|
||||
self.metatagRecalculator.registerSource(source)
|
||||
}
|
||||
}
|
||||
|
||||
function registerSource(source: FeatureSourceForLayer & Tiled) {
|
||||
|
||||
const sourceBBox = source.features.map(allFeatures => BBox.bboxAroundAll(allFeatures.map(f => BBox.get(f.feature))))
|
||||
|
||||
// Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering
|
||||
const doShowFeatures = source.features.map(
|
||||
f => {
|
||||
const z = self.locationControl.data.zoom
|
||||
clusterCounter.addTile(source)
|
||||
const sourceBBox = source.features.map(allFeatures => BBox.bboxAroundAll(allFeatures.map(f => BBox.get(f.feature))))
|
||||
|
||||
if (!source.layer.isDisplayed.data) {
|
||||
return false;
|
||||
}
|
||||
// Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering
|
||||
const doShowFeatures = source.features.map(
|
||||
f => {
|
||||
const z = self.locationControl.data.zoom
|
||||
|
||||
const bounds = self.currentBounds.data
|
||||
if (bounds === undefined) {
|
||||
// Map is not yet displayed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sourceBBox.data.overlapsWith(bounds)) {
|
||||
// Not within range -> features are hidden
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if (z < source.layer.layerDef.minzoom) {
|
||||
// Layer is always hidden for this zoom level
|
||||
return false;
|
||||
}
|
||||
|
||||
if (z > clustering.maxZoom) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (f.length > clustering.minNeededElements) {
|
||||
// This tile alone already has too much features
|
||||
return false
|
||||
}
|
||||
|
||||
let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex);
|
||||
if (tileZ >= z) {
|
||||
|
||||
while (tileZ > z) {
|
||||
tileZ--
|
||||
tileX = Math.floor(tileX / 2)
|
||||
tileY = Math.floor(tileY / 2)
|
||||
}
|
||||
|
||||
if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) {
|
||||
// To much elements
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}, [this.currentBounds, source.layer.isDisplayed, sourceBBox]
|
||||
)
|
||||
|
||||
new ShowDataLayer(
|
||||
{
|
||||
features: source,
|
||||
leafletMap: self.leafletMap,
|
||||
layerToShow: source.layer.layerDef,
|
||||
doShowLayer: doShowFeatures,
|
||||
selectedElement: self.selectedElement,
|
||||
state: self,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, self)
|
||||
if (!source.layer.isDisplayed.data) {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}, this
|
||||
);
|
||||
|
||||
const bounds = self.currentBounds.data
|
||||
if (bounds === undefined) {
|
||||
// Map is not yet displayed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sourceBBox.data.overlapsWith(bounds)) {
|
||||
// Not within range -> features are hidden
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if (z < source.layer.layerDef.minzoom) {
|
||||
// Layer is always hidden for this zoom level
|
||||
return false;
|
||||
}
|
||||
|
||||
if (z > clustering.maxZoom) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (f.length > clustering.minNeededElements) {
|
||||
// This tile alone already has too much features
|
||||
return false
|
||||
}
|
||||
|
||||
let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex);
|
||||
if (tileZ >= z) {
|
||||
|
||||
while (tileZ > z) {
|
||||
tileZ--
|
||||
tileX = Math.floor(tileX / 2)
|
||||
tileY = Math.floor(tileY / 2)
|
||||
}
|
||||
|
||||
if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) {
|
||||
// To much elements
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}, [self.currentBounds, source.layer.isDisplayed, sourceBBox]
|
||||
)
|
||||
|
||||
new ShowDataLayer(
|
||||
{
|
||||
features: source,
|
||||
leafletMap: self.leafletMap,
|
||||
layerToShow: source.layer.layerDef,
|
||||
doShowLayer: doShowFeatures,
|
||||
selectedElement: self.selectedElement,
|
||||
state: self,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, self)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
this.featurePipeline = new FeaturePipeline(registerSource, this, {handleRawFeatureSource: registerRaw});
|
||||
this.metatagRecalculator = new MetaTagRecalculator(this, this.featurePipeline)
|
||||
this.metatagRecalculator.registerSource(this.currentView, true)
|
||||
|
||||
sourcesToRegister.forEach(source => self.metatagRecalculator.registerSource(source))
|
||||
|
||||
new SelectedFeatureHandler(Hash.hash, this)
|
||||
|
||||
this.AddClusteringToMap(this.leafletMap)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue