forked from MapComplete/MapComplete
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]
|
const localValue = currentTags[key]
|
||||||
if (localValue !== osmValue) {
|
if (localValue !== osmValue) {
|
||||||
console.log("Local value for ", key, ":", localValue, "upstream", osmValue)
|
|
||||||
somethingChanged = true;
|
somethingChanged = true;
|
||||||
currentTags[key] = osmValue
|
currentTags[key] = osmValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,12 @@ export class BBox {
|
||||||
}
|
}
|
||||||
return feature.bbox;
|
return feature.bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bboxAroundAll(bboxes: BBox[]): BBox{
|
static bboxAroundAll(bboxes: BBox[]): BBox {
|
||||||
let maxLat: number = -90;
|
let maxLat: number = -90;
|
||||||
let maxLon: number= -180;
|
let maxLon: number = -180;
|
||||||
let minLat: number= 80;
|
let minLat: number = 80;
|
||||||
let minLon: number= 180;
|
let minLon: number = 180;
|
||||||
|
|
||||||
for (const bbox of bboxes) {
|
for (const bbox of bboxes) {
|
||||||
maxLat = Math.max(maxLat, bbox.maxLat)
|
maxLat = Math.max(maxLat, bbox.maxLat)
|
||||||
|
@ -61,7 +61,7 @@ export class BBox {
|
||||||
minLat = Math.min(minLat, bbox.minLat)
|
minLat = Math.min(minLat, bbox.minLat)
|
||||||
minLon = Math.min(minLon, bbox.minLon)
|
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 {
|
static fromTile(z: number, x: number, y: number): BBox {
|
||||||
|
@ -75,6 +75,14 @@ export class BBox {
|
||||||
return BBox.fromTile(...Tiles.tile_from_index(i))
|
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)
|
* Constructs a tilerange which fully contains this bbox (thus might be a bit larger)
|
||||||
* @param zoomlevel
|
* @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 TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
|
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
|
||||||
import MetaTagging from "../MetaTagging";
|
|
||||||
import RememberingSource from "./Sources/RememberingSource";
|
import RememberingSource from "./Sources/RememberingSource";
|
||||||
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
|
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
|
||||||
import GeoJsonSource from "./Sources/GeoJsonSource";
|
import GeoJsonSource from "./Sources/GeoJsonSource";
|
||||||
|
@ -49,7 +48,7 @@ export default class FeaturePipeline {
|
||||||
|
|
||||||
private readonly overpassUpdater: OverpassFeatureSource
|
private readonly overpassUpdater: OverpassFeatureSource
|
||||||
private state: MapState;
|
private state: MapState;
|
||||||
private readonly relationTracker: RelationsTracker
|
public readonly relationTracker: RelationsTracker
|
||||||
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,9 +62,7 @@ export default class FeaturePipeline {
|
||||||
private readonly osmSourceZoomLevel
|
private readonly osmSourceZoomLevel
|
||||||
|
|
||||||
private readonly localStorageSavers = new Map<string, SaveTileToLocalStorageActor>()
|
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.
|
* Keeps track of all raw OSM-nodes.
|
||||||
* Only initialized if 'type_node' is defined as layer
|
* Only initialized if 'type_node' is defined as layer
|
||||||
|
@ -74,7 +71,12 @@ export default class FeaturePipeline {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
|
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;
|
this.state = state;
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
|
@ -102,10 +104,6 @@ export default class FeaturePipeline {
|
||||||
return location.zoom >= minzoom;
|
return location.zoom >= minzoom;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.requestMetataggingRecalculation.stabilized(500).addCallbackAndRunD(_ => {
|
|
||||||
self.updateAllMetaTagging("Request stabilized")
|
|
||||||
})
|
|
||||||
|
|
||||||
const neededTilesFromOsm = this.getNeededTilesFromOsm(this.sufficientlyZoomed)
|
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'
|
// Given a tile, wraps it and passes it on to render (handled by 'handleFeatureSource'
|
||||||
function patchedHandleFeatureSource(src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) {
|
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
|
// This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
|
||||||
const srcFiltered =
|
const withChanges = new ChangeGeometryApplicator(src, state.changes);
|
||||||
new FilteringFeatureSource(state, src.tileIndex,
|
const srcFiltered = new FilteringFeatureSource(state, src.tileIndex,withChanges)
|
||||||
new ChangeGeometryApplicator(src, state.changes),
|
|
||||||
self.metataggingRecalculated
|
|
||||||
)
|
|
||||||
|
|
||||||
handleFeatureSource(srcFiltered)
|
handleFeatureSource(srcFiltered)
|
||||||
|
if(options?.handleRawFeatureSource){
|
||||||
|
options.handleRawFeatureSource(withChanges)
|
||||||
|
}
|
||||||
self.somethingLoaded.setData(true)
|
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)
|
// 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") {
|
if (id === "current_view") {
|
||||||
handlePriviligedFeatureSource(state.currentView)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +317,6 @@ export default class FeaturePipeline {
|
||||||
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
||||||
// AT last, we always apply the metatags whenever possible
|
// AT last, we always apply the metatags whenever possible
|
||||||
perLayer.features.addCallbackAndRunD(feats => {
|
perLayer.features.addCallbackAndRunD(feats => {
|
||||||
console.log("New feature for layer ", perLayer.layer.layerDef.id, ":", feats)
|
|
||||||
self.onNewDataLoaded(perLayer);
|
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(
|
this.runningQuery = updater.runningQuery.map(
|
||||||
overpass => {
|
overpass => {
|
||||||
console.log("FeaturePipeline: runningQuery state changed: Overpass", overpass ? "is querying," : "is idle,",
|
console.log("FeaturePipeline: runningQuery state changed: Overpass", overpass ? "is querying," : "is idle,",
|
||||||
|
@ -354,7 +339,6 @@ export default class FeaturePipeline {
|
||||||
|
|
||||||
private onNewDataLoaded(src: FeatureSource){
|
private onNewDataLoaded(src: FeatureSource){
|
||||||
this.newDataLoadedSignal.setData(src)
|
this.newDataLoadedSignal.setData(src)
|
||||||
this.requestMetataggingRecalculation.setData(new Date())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetAllFeaturesWithin(bbox: BBox): any[][] {
|
public GetAllFeaturesWithin(bbox: BBox): any[][] {
|
||||||
|
@ -501,44 +485,4 @@ export default class FeaturePipeline {
|
||||||
return updater;
|
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;
|
const coor = feature.geometry.coordinates;
|
||||||
for (const otherFeature of otherFeatures) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ export class GeoOperations {
|
||||||
|
|
||||||
for (const otherFeature of otherFeatures) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export class GeoOperations {
|
||||||
|
|
||||||
for (const otherFeature of otherFeatures) {
|
for (const otherFeature of otherFeatures) {
|
||||||
|
|
||||||
if (feature.id === otherFeature.id) {
|
if (feature.properties.id !== undefined && feature.properties.id === otherFeature.properties.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger";
|
import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger";
|
||||||
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
|
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
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 }[],
|
public static addMetatags(features: { feature: any; freshness: Date }[],
|
||||||
params: ExtraFuncParams,
|
params: ExtraFuncParams,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig,
|
||||||
state,
|
state?: {allElements?: ElementStorage},
|
||||||
options?: {
|
options?: {
|
||||||
includeDates?: true | boolean,
|
includeDates?: true | boolean,
|
||||||
includeNonDates?: true | boolean
|
includeNonDates?: true | boolean
|
||||||
|
@ -35,11 +36,11 @@ export default class MetaTagging {
|
||||||
const metatagsToApply: SimpleMetaTagger[] = []
|
const metatagsToApply: SimpleMetaTagger[] = []
|
||||||
for (const metatag of SimpleMetaTaggers.metatags) {
|
for (const metatag of SimpleMetaTaggers.metatags) {
|
||||||
if (metatag.includesDates) {
|
if (metatag.includesDates) {
|
||||||
if (options.includeDates ?? true) {
|
if (options?.includeDates ?? true) {
|
||||||
metatagsToApply.push(metatag)
|
metatagsToApply.push(metatag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.includeNonDates ?? true) {
|
if (options?.includeNonDates ?? true) {
|
||||||
metatagsToApply.push(metatag)
|
metatagsToApply.push(metatag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +104,8 @@ export default class MetaTagging {
|
||||||
}
|
}
|
||||||
return atLeastOneFeatureChanged
|
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)[] = [];
|
const functions: ((feature: any) => any)[] = [];
|
||||||
for (const entry of calculatedTags) {
|
for (const entry of calculatedTags) {
|
||||||
const key = entry[0]
|
const key = entry[0]
|
||||||
|
|
|
@ -9,7 +9,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||||
import {CountryCoder} from "latlon2country"
|
import {CountryCoder} from "latlon2country"
|
||||||
|
|
||||||
|
|
||||||
export class SimpleMetaTagger {
|
export class SimpleMetaTagger {
|
||||||
public readonly keys: string[];
|
public readonly keys: string[];
|
||||||
public readonly doc: string;
|
public readonly doc: string;
|
||||||
public readonly isLazy: boolean;
|
public readonly isLazy: boolean;
|
||||||
|
@ -42,9 +42,9 @@ export class SimpleMetaTagger {
|
||||||
export class CountryTagger extends SimpleMetaTagger {
|
export class CountryTagger extends SimpleMetaTagger {
|
||||||
private static readonly coder = new CountryCoder("https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country", Utils.downloadJson);
|
private static readonly coder = new CountryCoder("https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country", Utils.downloadJson);
|
||||||
public runningTasks: Set<any>;
|
public runningTasks: Set<any>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const runningTasks= new Set<any>();
|
const runningTasks = new Set<any>();
|
||||||
super
|
super
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
@ -77,20 +77,12 @@ export class CountryTagger extends SimpleMetaTagger {
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.runningTasks = runningTasks;
|
this.runningTasks = runningTasks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SimpleMetaTaggers {
|
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(
|
public static readonly objectMetaInfo = new SimpleMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_last_edit:contributor",
|
keys: ["_last_edit:contributor",
|
||||||
|
@ -121,7 +113,25 @@ export default class SimpleMetaTaggers {
|
||||||
return true;
|
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({
|
private static latlon = new SimpleMetaTagger({
|
||||||
keys: ["_lat", "_lon"],
|
keys: ["_lat", "_lon"],
|
||||||
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
|
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;
|
return true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
private static canonicalize = new SimpleMetaTagger(
|
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`)",
|
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
|
return rewritten
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
private static lngth = new SimpleMetaTagger(
|
private static lngth = new SimpleMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_length", "_length:km"],
|
keys: ["_length", "_length:km"],
|
||||||
|
@ -269,7 +277,6 @@ export default class SimpleMetaTaggers {
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
public static country = new CountryTagger()
|
|
||||||
private static isOpen = new SimpleMetaTagger(
|
private static isOpen = new SimpleMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_isOpen"],
|
keys: ["_isOpen"],
|
||||||
|
@ -277,23 +284,33 @@ export default class SimpleMetaTaggers {
|
||||||
includesDates: true,
|
includesDates: true,
|
||||||
isLazy: true
|
isLazy: true
|
||||||
},
|
},
|
||||||
((feature, _, __ ,state) => {
|
((feature, _, __, state) => {
|
||||||
if (Utils.runningFromConsole) {
|
if (Utils.runningFromConsole) {
|
||||||
// We are running from console, thus probably creating a cache
|
// We are running from console, thus probably creating a cache
|
||||||
// isOpen is irrelevant
|
// isOpen is irrelevant
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if(feature.properties.opening_hours === "24/7"){
|
||||||
|
feature.properties._isOpen = "yes"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Object.defineProperty(feature.properties, "_isOpen", {
|
Object.defineProperty(feature.properties, "_isOpen", {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => {
|
get: () => {
|
||||||
|
if(feature.properties.id === "node/7464543832"){
|
||||||
|
console.log("Getting _isOpen for ", feature.properties.i)
|
||||||
|
}
|
||||||
delete feature.properties._isOpen
|
delete feature.properties._isOpen
|
||||||
feature.properties._isOpen = undefined
|
feature.properties._isOpen = undefined
|
||||||
const tagsSource = state.allElements.getEventSourceById(feature.properties.id);
|
const tagsSource = state.allElements.getEventSourceById(feature.properties.id);
|
||||||
tagsSource
|
tagsSource.addCallbackAndRunD(tags => {
|
||||||
.addCallbackAndRunD(tags => {
|
// Install a listener to the tags...
|
||||||
if (tags.opening_hours === undefined || tags._country === undefined) {
|
if (tags.opening_hours === undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(tags._country === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -305,18 +322,20 @@ export default class SimpleMetaTaggers {
|
||||||
country_code: tags._country.toLowerCase()
|
country_code: tags._country.toLowerCase()
|
||||||
}
|
}
|
||||||
}, {tag_key: "opening_hours"});
|
}, {tag_key: "opening_hours"});
|
||||||
// AUtomatically triggered on the next change
|
|
||||||
|
// AUtomatically triggered on the next change (and a bit below)
|
||||||
const updateTags = () => {
|
const updateTags = () => {
|
||||||
const oldValueIsOpen = tags["_isOpen"];
|
const oldValueIsOpen = tags["_isOpen"];
|
||||||
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
|
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
|
||||||
|
|
||||||
if (oldNextChange > (new Date()).getTime() &&
|
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) {
|
&& tags["_isOpen"] !== undefined) {
|
||||||
// Already calculated and should not yet be triggered
|
// Already calculated and should not yet be triggered
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recalculate!
|
||||||
tags["_isOpen"] = oh.getState() ? "yes" : "no";
|
tags["_isOpen"] = oh.getState() ? "yes" : "no";
|
||||||
const comment = oh.getComment();
|
const comment = oh.getComment();
|
||||||
if (comment) {
|
if (comment) {
|
||||||
|
@ -380,8 +399,6 @@ export default class SimpleMetaTaggers {
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
private static currentTime = new SimpleMetaTagger(
|
private static currentTime = new SimpleMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"],
|
keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"],
|
||||||
|
@ -410,20 +427,6 @@ export default class SimpleMetaTaggers {
|
||||||
return true;
|
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[] = [
|
public static metatags: SimpleMetaTagger[] = [
|
||||||
SimpleMetaTaggers.latlon,
|
SimpleMetaTaggers.latlon,
|
||||||
SimpleMetaTaggers.layerInfo,
|
SimpleMetaTaggers.layerInfo,
|
||||||
|
@ -439,11 +442,9 @@ export default class SimpleMetaTaggers {
|
||||||
SimpleMetaTaggers.geometryType
|
SimpleMetaTaggers.geometryType
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy)
|
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy)
|
||||||
.map(tagger => tagger.keys));
|
.map(tagger => tagger.keys));
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme.
|
* Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme.
|
||||||
* These changes are performed in-place.
|
* These changes are performed in-place.
|
||||||
|
|
|
@ -10,6 +10,8 @@ import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler";
|
||||||
import Hash from "../Web/Hash";
|
import Hash from "../Web/Hash";
|
||||||
import {BBox} from "../BBox";
|
import {BBox} from "../BBox";
|
||||||
import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox";
|
import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox";
|
||||||
|
import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource";
|
||||||
|
import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator";
|
||||||
|
|
||||||
export default class FeaturePipelineState extends MapState {
|
export default class FeaturePipelineState extends MapState {
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ export default class FeaturePipelineState extends MapState {
|
||||||
*/
|
*/
|
||||||
public readonly featurePipeline: FeaturePipeline;
|
public readonly featurePipeline: FeaturePipeline;
|
||||||
private readonly featureAggregator: TileHierarchyAggregator;
|
private readonly featureAggregator: TileHierarchyAggregator;
|
||||||
|
private readonly metatagRecalculator : MetaTagRecalculator
|
||||||
constructor(layoutToUse: LayoutConfig) {
|
constructor(layoutToUse: LayoutConfig) {
|
||||||
super(layoutToUse);
|
super(layoutToUse);
|
||||||
|
|
||||||
|
@ -26,81 +28,106 @@ export default class FeaturePipelineState extends MapState {
|
||||||
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this);
|
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this);
|
||||||
const clusterCounter = this.featureAggregator
|
const clusterCounter = this.featureAggregator
|
||||||
const self = this;
|
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))))
|
clusterCounter.addTile(source)
|
||||||
|
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
|
|
||||||
|
|
||||||
if (!source.layer.isDisplayed.data) {
|
// Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering
|
||||||
return false;
|
const doShowFeatures = source.features.map(
|
||||||
}
|
f => {
|
||||||
|
const z = self.locationControl.data.zoom
|
||||||
|
|
||||||
const bounds = self.currentBounds.data
|
if (!source.layer.isDisplayed.data) {
|
||||||
if (bounds === undefined) {
|
return false;
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}, 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)
|
new SelectedFeatureHandler(Hash.hash, this)
|
||||||
|
|
||||||
this.AddClusteringToMap(this.leafletMap)
|
this.AddClusteringToMap(this.leafletMap)
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
||||||
"geoJsonZoomLevel": 10,
|
"geoJsonZoomLevel": 10,
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"minzoom": 12,
|
"minzoom": Math.min(12, layerJson.minzoom - 2),
|
||||||
"title": {
|
"title": {
|
||||||
"render": t.popupTitle.Subs({title}).translations
|
"render": t.popupTitle.Subs({title}).translations
|
||||||
},
|
},
|
||||||
|
|
|
@ -214,6 +214,7 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
|
||||||
if (!hasMinimap) {
|
if (!hasMinimap) {
|
||||||
layerConfig = {...layerConfig}
|
layerConfig = {...layerConfig}
|
||||||
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
|
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
|
||||||
|
layerConfig.tagRenderings.push(state.tagRenderings.get("questions"))
|
||||||
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
|
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,8 +161,6 @@ class AutomationPanel extends Combine{
|
||||||
whenDone("empty")
|
whenDone("empty")
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
stateToShow.setData("Applying metatags")
|
|
||||||
pipeline.updateAllMetaTagging("triggered by automaton")
|
|
||||||
stateToShow.setData("Gathering applicable elements")
|
stateToShow.setData("Gathering applicable elements")
|
||||||
|
|
||||||
let handled = 0
|
let handled = 0
|
||||||
|
@ -178,7 +176,7 @@ class AutomationPanel extends Combine{
|
||||||
const feature = ffs.feature
|
const feature = ffs.feature
|
||||||
const renderingTr = targetAction.GetRenderValue(feature.properties)
|
const renderingTr = targetAction.GetRenderValue(feature.properties)
|
||||||
const rendering = renderingTr.txt
|
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)
|
const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering)
|
||||||
.map(obj => obj.special))
|
.map(obj => obj.special))
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||||
import ValidatedTextField from "../Input/ValidatedTextField";
|
import ValidatedTextField from "../Input/ValidatedTextField";
|
||||||
import {QueryParameters} from "../../Logic/Web/QueryParameters";
|
import {QueryParameters} from "../../Logic/Web/QueryParameters";
|
||||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
|
import {InputElement} from "../Input/InputElement";
|
||||||
|
import {DropDown} from "../Input/DropDown";
|
||||||
|
|
||||||
export default class FilterView extends VariableUiElement {
|
export default class FilterView extends VariableUiElement {
|
||||||
constructor(filteredLayer: UIEventSource<FilteredLayer[]>, tileLayers: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]) {
|
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) => ({
|
const values : FilterState[] = options.map((f, i) => ({
|
||||||
currentFilter: f.osmTags, state: i
|
currentFilter: f.osmTags, state: i
|
||||||
}))
|
}))
|
||||||
const radio = new RadioButton(
|
let filterPicker : InputElement<number>
|
||||||
options.map(
|
|
||||||
(option, i) =>
|
if(options.length <= 6){
|
||||||
new FixedInputElement(option.question.Clone().SetClass("block"), i)
|
filterPicker = new RadioButton(
|
||||||
),
|
options.map(
|
||||||
{
|
(option, i) =>
|
||||||
dontStyle: true
|
new FixedInputElement(option.question.Clone().SetClass("block"), i)
|
||||||
}
|
),
|
||||||
);
|
{
|
||||||
return [radio,
|
dontStyle: true
|
||||||
radio.GetValue().map(
|
}
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
filterPicker = new DropDown("", options.map((option, i) => ({
|
||||||
|
value: i, shown: option.question.Clone()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
return [filterPicker,
|
||||||
|
filterPicker.GetValue().map(
|
||||||
i => values[i],
|
i => values[i],
|
||||||
[],
|
[],
|
||||||
selected => {
|
selected => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||||
|
import Loading from "./Base/Loading";
|
||||||
|
|
||||||
export default class CenterMessageBox extends VariableUiElement {
|
export default class CenterMessageBox extends VariableUiElement {
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ export default class CenterMessageBox extends VariableUiElement {
|
||||||
const message = updater.runningQuery.map(
|
const message = updater.runningQuery.map(
|
||||||
isRunning => {
|
isRunning => {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
return {el: t.loadingData};
|
return {el: new Loading(t.loadingData)};
|
||||||
}
|
}
|
||||||
if (!updater.sufficientlyZoomed.data) {
|
if (!updater.sufficientlyZoomed.data) {
|
||||||
return {el: t.zoomIn}
|
return {el: t.zoomIn}
|
||||||
|
@ -26,8 +27,8 @@ export default class CenterMessageBox extends VariableUiElement {
|
||||||
|
|
||||||
super(message.map(toShow => toShow.el))
|
super(message.map(toShow => toShow.el))
|
||||||
|
|
||||||
this.SetClass("block " +
|
this.SetClass("flex justify-around " +
|
||||||
"rounded-3xl bg-white text-xl font-bold text-center pointer-events-none p-4")
|
"rounded-3xl bg-white text-xl font-bold pointer-events-none p-4")
|
||||||
this.SetStyle("transition: opacity 750ms linear")
|
this.SetStyle("transition: opacity 750ms linear")
|
||||||
|
|
||||||
message.addCallbackAndRun(toShow => {
|
message.addCallbackAndRun(toShow => {
|
||||||
|
|
|
@ -658,6 +658,6 @@
|
||||||
],
|
],
|
||||||
"hideFromOverview": true,
|
"hideFromOverview": true,
|
||||||
"defaultBackgroundId": "AGIVFlandersGRB",
|
"defaultBackgroundId": "AGIVFlandersGRB",
|
||||||
"overpassMaxZoom": 15,
|
"overpassMaxZoom": 17,
|
||||||
"osmApiTileSize": 17
|
"osmApiTileSize": 17
|
||||||
}
|
}
|
7
test.ts
7
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 {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")
|
Loading…
Reference in a new issue