Performance optimazations

This commit is contained in:
pietervdvn 2022-01-26 20:47:08 +01:00
parent 632e7e9f9a
commit d2b245ab54
15 changed files with 321 additions and 214 deletions

View file

@ -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
}

View file

@ -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

View 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()
}
}
})
}
}

View file

@ -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()
}
}

View file

@ -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;
}

View file

@ -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]

View file

@ -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.

View file

@ -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)

View file

@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
"geoJsonZoomLevel": 10,
"maxCacheAge": 0
},
"minzoom": 12,
"minzoom": Math.min(12, layerJson.minzoom - 2),
"title": {
"render": t.popupTitle.Subs({title}).translations
},

View file

@ -214,6 +214,7 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
if (!hasMinimap) {
layerConfig = {...layerConfig}
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
layerConfig.tagRenderings.push(state.tagRenderings.get("questions"))
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
}

View file

@ -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("<a href='https://openstreetmap.org/"+feature.properties.id+"' target='_blank'>"+feature.properties.id+"</a>: "+new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties), state).ConstructElement().innerText)
log.push("<a href='https://openstreetmap.org/"+feature.properties.id+"' target='_blank'>"+feature.properties.id+"</a>: "+new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties), undefined).ConstructElement().innerText)
const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering)
.map(obj => obj.special))
for (const action of actions) {

View file

@ -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<FilteredLayer[]>, tileLayers: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]) {
@ -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<number>
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 => {

View file

@ -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 => {

View file

@ -658,6 +658,6 @@
],
"hideFromOverview": true,
"defaultBackgroundId": "AGIVFlandersGRB",
"overpassMaxZoom": 15,
"overpassMaxZoom": 17,
"osmApiTileSize": 17
}

View file

@ -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")
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")