Performance optimazations

This commit is contained in:
Pieter Vander Vennet 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)