From d2b245ab5440877c2b9b9965d5e504bdf693a9d5 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 26 Jan 2022 20:47:08 +0100 Subject: [PATCH] Performance optimazations --- Logic/Actors/SelectedElementTagsUpdater.ts | 1 - Logic/BBox.ts | 20 ++- .../Actors/MetaTagRecalculator.ts | 112 ++++++++++++ Logic/FeatureSource/FeaturePipeline.ts | 82 ++------- Logic/GeoOperations.ts | 6 +- Logic/MetaTagging.ts | 10 +- Logic/SimpleMetaTagger.ts | 79 ++++---- Logic/State/FeaturePipelineState.ts | 169 ++++++++++-------- .../Conversion/CreateNoteImportLayer.ts | 2 +- Models/ThemeConfig/Conversion/PrepareTheme.ts | 1 + UI/AutomatonGui.ts | 4 +- UI/BigComponents/FilterView.ts | 33 ++-- UI/CenterMessageBox.ts | 7 +- assets/themes/grb_import/grb.json | 2 +- test.ts | 7 +- 15 files changed, 321 insertions(+), 214 deletions(-) create mode 100644 Logic/FeatureSource/Actors/MetaTagRecalculator.ts diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 2aa2c942ef..beff7c21f3 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -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 } diff --git a/Logic/BBox.ts b/Logic/BBox.ts index fb0625886a..1aa9d08827 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -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 diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts new file mode 100644 index 0000000000..139091b054 --- /dev/null +++ b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts @@ -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() + 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 = new Set() +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() + } + } + }) + + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 3b6bf45721..9eb233c366 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -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; /** @@ -63,9 +62,7 @@ export default class FeaturePipeline { private readonly osmSourceZoomLevel private readonly localStorageSavers = new Map() - private readonly metataggingRecalculated = new UIEventSource(undefined) - private readonly requestMetataggingRecalculation = new UIEventSource(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, 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, this.state, `${reason} (tile ${tile.tileIndex})`) - }) - }) - self.metataggingRecalculated.ping() - - } - } \ No newline at end of file diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 723e70009a..0a4152aef9 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -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; } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index a48dae3d90..6479a22216 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -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] diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 67be6a5af0..f0b28605d6 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -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; - + constructor() { - const runningTasks= new Set(); + const runningTasks = new Set(); 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. diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 02c2cd1bfa..35d43bb9d9 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -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) diff --git a/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts b/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts index 2e5d2a8b74..0491fb696d 100644 --- a/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts +++ b/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts @@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion { if (!hasMinimap) { layerConfig = {...layerConfig} layerConfig.tagRenderings = [...layerConfig.tagRenderings] + layerConfig.tagRenderings.push(state.tagRenderings.get("questions")) layerConfig.tagRenderings.push(state.tagRenderings.get("minimap")) } diff --git a/UI/AutomatonGui.ts b/UI/AutomatonGui.ts index b462da25ce..6bcafbd73b 100644 --- a/UI/AutomatonGui.ts +++ b/UI/AutomatonGui.ts @@ -161,8 +161,6 @@ class AutomationPanel extends Combine{ whenDone("empty") return true; } - stateToShow.setData("Applying metatags") - pipeline.updateAllMetaTagging("triggered by automaton") stateToShow.setData("Gathering applicable elements") let handled = 0 @@ -178,7 +176,7 @@ class AutomationPanel extends Combine{ const feature = ffs.feature const renderingTr = targetAction.GetRenderValue(feature.properties) const rendering = renderingTr.txt - log.push(""+feature.properties.id+": "+new SubstitutedTranslation(renderingTr, new UIEventSource(feature.properties), state).ConstructElement().innerText) + log.push(""+feature.properties.id+": "+new SubstitutedTranslation(renderingTr, new UIEventSource(feature.properties), undefined).ConstructElement().innerText) const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering) .map(obj => obj.special)) for (const action of actions) { diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index e4cf5eaf7b..678203e691 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -18,6 +18,8 @@ import {SubstitutedTranslation} from "../SubstitutedTranslation"; import ValidatedTextField from "../Input/ValidatedTextField"; import {QueryParameters} from "../../Logic/Web/QueryParameters"; import {TagUtils} from "../../Logic/Tags/TagUtils"; +import {InputElement} from "../Input/InputElement"; +import {DropDown} from "../Input/DropDown"; export default class FilterView extends VariableUiElement { constructor(filteredLayer: UIEventSource, tileLayers: { config: TilesourceConfig, isDisplayed: UIEventSource }[]) { @@ -242,17 +244,26 @@ export default class FilterView extends VariableUiElement { const values : FilterState[] = options.map((f, i) => ({ currentFilter: f.osmTags, state: i })) - const radio = new RadioButton( - options.map( - (option, i) => - new FixedInputElement(option.question.Clone().SetClass("block"), i) - ), - { - dontStyle: true - } - ); - return [radio, - radio.GetValue().map( + let filterPicker : InputElement + + if(options.length <= 6){ + filterPicker = new RadioButton( + options.map( + (option, i) => + new FixedInputElement(option.question.Clone().SetClass("block"), i) + ), + { + dontStyle: true + } + ); + }else{ + filterPicker = new DropDown("", options.map((option, i) => ({ + value: i, shown: option.question.Clone() + }))) + } + + return [filterPicker, + filterPicker.GetValue().map( i => values[i], [], selected => { diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 500fc4609d..bee5f31bb8 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -1,6 +1,7 @@ import Translations from "./i18n/Translations"; import {VariableUiElement} from "./Base/VariableUIElement"; import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; +import Loading from "./Base/Loading"; export default class CenterMessageBox extends VariableUiElement { @@ -10,7 +11,7 @@ export default class CenterMessageBox extends VariableUiElement { const message = updater.runningQuery.map( isRunning => { if (isRunning) { - return {el: t.loadingData}; + return {el: new Loading(t.loadingData)}; } if (!updater.sufficientlyZoomed.data) { return {el: t.zoomIn} @@ -26,8 +27,8 @@ export default class CenterMessageBox extends VariableUiElement { super(message.map(toShow => toShow.el)) - this.SetClass("block " + - "rounded-3xl bg-white text-xl font-bold text-center pointer-events-none p-4") + this.SetClass("flex justify-around " + + "rounded-3xl bg-white text-xl font-bold pointer-events-none p-4") this.SetStyle("transition: opacity 750ms linear") message.addCallbackAndRun(toShow => { diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index 7fcaf70e9c..74f059e865 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -658,6 +658,6 @@ ], "hideFromOverview": true, "defaultBackgroundId": "AGIVFlandersGRB", - "overpassMaxZoom": 15, + "overpassMaxZoom": 17, "osmApiTileSize": 17 } \ No newline at end of file diff --git a/test.ts b/test.ts index ce18209805..f213a0fda4 100644 --- a/test.ts +++ b/test.ts @@ -1,4 +1,7 @@ +import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; +import List from "./UI/Base/List"; +import Link from "./UI/Base/Link"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import Loading from "./UI/Base/Loading"; -new Loading(new FixedUiElement("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")).AttachTo("maindiv") \ No newline at end of file +const allHidden = AllKnownLayouts.layoutsList.filter(l => l.hideFromOverview) +new List(allHidden.map(th => new Link(new FixedUiElement(th.id), "theme.html?layout="+th.id))).AttachTo("maindiv") \ No newline at end of file