Reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2022-09-08 21:40:48 +02:00
parent e22d189376
commit b541d3eab4
382 changed files with 50893 additions and 35566 deletions

View file

@ -1,12 +1,13 @@
/**
* The data layer shows all the given geojson elements with the appropriate icon etc
*/
import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
export default class ShowDataLayer {
public static actualContstructor : (options: ShowDataLayerOptions & { layerToShow: LayerConfig }) => void = undefined;
public static actualContstructor: (
options: ShowDataLayerOptions & { layerToShow: LayerConfig }
) => void = undefined
/**
* Creates a datalayer.
@ -15,11 +16,9 @@ export default class ShowDataLayer {
* @param options
*/
constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
if(ShowDataLayer.actualContstructor === undefined){
if (ShowDataLayer.actualContstructor === undefined) {
throw "Show data layer is called, but it isn't initialized yet. Call ` ShowDataLayer.actualContstructor = (options => new ShowDataLayerImplementation(options)) ` somewhere, e.g. in your init"
}
ShowDataLayer.actualContstructor(options)
}
}
}

View file

@ -1,9 +1,9 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
import {ElementStorage} from "../../Logic/ElementStorage";
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
import { ElementStorage } from "../../Logic/ElementStorage"
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
/*
// import 'leaflet-polylineoffset';
We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object.
@ -18,23 +18,22 @@ We don't actually import it here. It is imported in the 'MinimapImplementation'-
* The data layer shows all the given geojson elements with the appropriate icon etc
*/
export default class ShowDataLayerImplementation {
private static dataLayerIds = 0
private readonly _leafletMap: Store<L.Map>;
private readonly _enablePopups: boolean;
private readonly _leafletMap: Store<L.Map>
private readonly _enablePopups: boolean
private readonly _features: RenderingMultiPlexerFeatureSource
private readonly _layerToShow: LayerConfig;
private readonly _layerToShow: LayerConfig
private readonly _selectedElement: UIEventSource<any>
private readonly allElements: ElementStorage
// Used to generate a fresh ID when needed
private _cleanCount = 0;
private geoLayer = undefined;
private _cleanCount = 0
private geoLayer = undefined
/**
* A collection of functions to call when the current geolayer is unregistered
*/
private unregister: (() => void)[] = [];
private isDirty = false;
private unregister: (() => void)[] = []
private isDirty = false
/**
* If the selected element triggers, this is used to lookup the correct layer and to open the popup
* Used to avoid a lot of callbacks on the selected element
@ -42,9 +41,12 @@ export default class ShowDataLayerImplementation {
* Note: the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations
* @private
*/
private readonly leafletLayersPerId = new Map<string, { feature: any, leafletlayer: any }>()
private readonly showDataLayerid: number;
private readonly createPopup: (tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen
private readonly leafletLayersPerId = new Map<string, { feature: any; leafletlayer: any }>()
private readonly showDataLayerid: number
private readonly createPopup: (
tags: UIEventSource<any>,
layer: LayerConfig
) => ScrollableFullScreen
/**
* Creates a datalayer.
@ -53,38 +55,40 @@ export default class ShowDataLayerImplementation {
* @param options
*/
constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
this._leafletMap = options.leafletMap;
this.showDataLayerid = ShowDataLayerImplementation.dataLayerIds;
this._leafletMap = options.leafletMap
this.showDataLayerid = ShowDataLayerImplementation.dataLayerIds
ShowDataLayerImplementation.dataLayerIds++
if (options.features === undefined) {
console.error("Invalid ShowDataLayer invocation: options.features is undefed")
throw "Invalid ShowDataLayer invocation: options.features is undefed"
}
this._features = new RenderingMultiPlexerFeatureSource(options.features, options.layerToShow);
this._layerToShow = options.layerToShow;
this._features = new RenderingMultiPlexerFeatureSource(
options.features,
options.layerToShow
)
this._layerToShow = options.layerToShow
this._selectedElement = options.selectedElement
this.allElements = options.state?.allElements;
this.createPopup = undefined;
this._enablePopups = options.popup !== undefined;
this.allElements = options.state?.allElements
this.createPopup = undefined
this._enablePopups = options.popup !== undefined
if (options.popup !== undefined) {
this.createPopup = options.popup
}
const self = this;
const self = this
options.leafletMap.addCallback(_ => {
return self.update(options)
}
);
options.leafletMap.addCallback((_) => {
return self.update(options)
})
this._features.features.addCallback(_ => self.update(options));
options.doShowLayer?.addCallback(doShow => {
const mp = options.leafletMap.data;
this._features.features.addCallback((_) => self.update(options))
options.doShowLayer?.addCallback((doShow) => {
const mp = options.leafletMap.data
if (mp === null) {
self.Destroy()
return true;
return true
}
if (mp == undefined) {
return;
return
}
if (doShow) {
@ -96,24 +100,21 @@ export default class ShowDataLayerImplementation {
} else {
if (this.geoLayer !== undefined) {
mp.removeLayer(this.geoLayer)
this.unregister.forEach(f => f())
this.unregister.forEach((f) => f())
this.unregister = []
}
}
})
this._selectedElement?.addCallbackAndRunD(selected => {
this._selectedElement?.addCallbackAndRunD((selected) => {
self.openPopupOfSelectedElement(selected)
})
this.update(options)
}
private Destroy() {
this.unregister.forEach(f => f())
this.unregister.forEach((f) => f())
}
private openPopupOfSelectedElement(selected) {
@ -121,28 +122,29 @@ export default class ShowDataLayerImplementation {
return
}
if (this._leafletMap.data === undefined) {
return;
return
}
const v = this.leafletLayersPerId.get(selected.properties.id + selected.geometry.type)
if (v === undefined) {
return;
return
}
const leafletLayer = v.leafletlayer
const feature = v.feature
if (leafletLayer.getPopup().isOpen()) {
return;
return
}
if (selected.properties.id !== feature.properties.id) {
return;
return
}
if (feature.id !== feature.properties.id) {
// Probably a feature which has renamed
// the feature might have as id 'node/-1' and as 'feature.properties.id' = 'the newly assigned id'. That is no good too
console.log("Not opening the popup for", feature, "as probably renamed")
return;
return
}
if (selected.geometry.type === feature.geometry.type // If a feature is rendered both as way and as point, opening one popup might trigger the other to open, which might trigger the one to open again
if (
selected.geometry.type === feature.geometry.type // If a feature is rendered both as way and as point, opening one popup might trigger the other to open, which might trigger the one to open again
) {
leafletLayer.openPopup()
}
@ -150,41 +152,42 @@ export default class ShowDataLayerImplementation {
private update(options: ShowDataLayerOptions): boolean {
if (this._features.features.data === undefined) {
return;
return
}
this.isDirty = true;
this.isDirty = true
if (options?.doShowLayer?.data === false) {
return;
return
}
const mp = options.leafletMap.data;
const mp = options.leafletMap.data
if (mp === null) {
return true; // Unregister as the map has been destroyed
return true // Unregister as the map has been destroyed
}
if (mp === undefined) {
return;
return
}
this._cleanCount++
// clean all the old stuff away, if any
if (this.geoLayer !== undefined) {
mp.removeLayer(this.geoLayer);
mp.removeLayer(this.geoLayer)
}
const self = this;
const self = this
const data = {
type: "FeatureCollection",
features: []
features: [],
}
// @ts-ignore
this.geoLayer = L.geoJSON(data, {
style: feature => self.createStyleFor(feature),
style: (feature) => self.createStyleFor(feature),
pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng),
onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer)
});
onEachFeature: (feature, leafletLayer) =>
self.postProcessFeature(feature, leafletLayer),
})
const selfLayer = this.geoLayer;
const allFeats = this._features.features.data;
const selfLayer = this.geoLayer
const allFeats = this._features.features.data
for (const feat of allFeats) {
if (feat === undefined) {
continue
@ -192,10 +195,16 @@ export default class ShowDataLayerImplementation {
try {
if (feat.geometry.type === "LineString") {
const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates)
const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource<any>(feat.properties);
let offsettedLine;
const tagsSource =
this.allElements?.addOrGetElement(feat) ??
new UIEventSource<any>(feat.properties)
let offsettedLine
tagsSource
.map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags))
.map((tags) =>
this._layerToShow.lineRendering[
feat.lineRenderingIndex
].GenerateLeafletStyle(tags)
)
.withEqualityStabilized((a, b) => {
if (a === b) {
return true
@ -203,14 +212,19 @@ export default class ShowDataLayerImplementation {
if (a === undefined || b === undefined) {
return false
}
return a.offset === b.offset && a.color === b.color && a.weight === b.weight && a.dashArray === b.dashArray
return (
a.offset === b.offset &&
a.color === b.color &&
a.weight === b.weight &&
a.dashArray === b.dashArray
)
})
.addCallbackAndRunD(lineStyle => {
.addCallbackAndRunD((lineStyle) => {
if (offsettedLine !== undefined) {
self.geoLayer.removeLayer(offsettedLine)
}
// @ts-ignore
offsettedLine = L.polyline(coords, lineStyle);
offsettedLine = L.polyline(coords, lineStyle)
this.postProcessFeature(feat, offsettedLine)
offsettedLine.addTo(this.geoLayer)
@ -218,10 +232,16 @@ export default class ShowDataLayerImplementation {
return self.geoLayer !== selfLayer
})
} else {
this.geoLayer.addData(feat);
this.geoLayer.addData(feat)
}
} catch (e) {
console.error("Could not add ", feat, "to the geojson layer in leaflet due to", e, e.stack)
console.error(
"Could not add ",
feat,
"to the geojson layer in leaflet due to",
e,
e.stack
)
}
}
@ -229,7 +249,7 @@ export default class ShowDataLayerImplementation {
if (this.geoLayer.getLayers().length > 0) {
try {
const bounds = this.geoLayer.getBounds()
mp.fitBounds(bounds, {animate: false})
mp.fitBounds(bounds, { animate: false })
} catch (e) {
console.debug("Invalid bounds", e)
}
@ -239,13 +259,13 @@ export default class ShowDataLayerImplementation {
if (options.doShowLayer?.data ?? true) {
mp.addLayer(this.geoLayer)
}
this.isDirty = false;
this.isDirty = false
this.openPopupOfSelectedElement(this._selectedElement?.data)
}
private createStyleFor(feature) {
const tagsSource = this.allElements?.addOrGetElement(feature) ?? new UIEventSource<any>(feature.properties);
const tagsSource =
this.allElements?.addOrGetElement(feature) ?? new UIEventSource<any>(feature.properties)
// Every object is tied to exactly one layer
const layer = this._layerToShow
@ -253,9 +273,12 @@ export default class ShowDataLayerImplementation {
const lineRenderingIndex = feature.lineRenderingIndex
if (pointRenderingIndex !== undefined) {
const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups)
const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(
tagsSource,
this._enablePopups
)
return {
icon: style
icon: style,
}
}
if (lineRenderingIndex !== undefined) {
@ -272,19 +295,26 @@ export default class ShowDataLayerImplementation {
const layer: LayerConfig = this._layerToShow
if (layer === undefined) {
return;
return
}
let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(feature.properties)
const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) && this._enablePopups
let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(tagSource, clickable);
const baseElement = style.html;
let tagSource =
this.allElements?.getEventSourceById(feature.properties.id) ??
new UIEventSource<any>(feature.properties)
const clickable =
!(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) &&
this._enablePopups
let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(
tagSource,
clickable
)
const baseElement = style.html
if (!this._enablePopups) {
baseElement.SetStyle("cursor: initial !important")
}
style.html = style.html.ConstructElement()
return L.marker(latLng, {
icon: L.divIcon(style)
});
icon: L.divIcon(style),
})
}
/**
@ -298,53 +328,59 @@ export default class ShowDataLayerImplementation {
if (layer.title === undefined || !this._enablePopups) {
// No popup action defined -> Don't do anything
// or probably a map in the popup - no popups needed!
return;
return
}
const popup = L.popup({
autoPan: true,
closeOnEscapeKey: true,
closeButton: false,
autoPanPaddingTopLeft: [15, 15],
const popup = L.popup(
{
autoPan: true,
closeOnEscapeKey: true,
closeButton: false,
autoPanPaddingTopLeft: [15, 15],
},
leafletLayer
)
}, leafletLayer);
leafletLayer.bindPopup(popup)
leafletLayer.bindPopup(popup);
let infobox: ScrollableFullScreen = undefined;
const id = `popup-${feature.properties.id}-${feature.geometry.type}-${this.showDataLayerid}-${this._cleanCount}-${feature.pointRenderingIndex ?? feature.lineRenderingIndex}-${feature.multiLineStringIndex ?? ""}`
popup.setContent(`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading</div>`)
const createpopup = this.createPopup;
let infobox: ScrollableFullScreen = undefined
const id = `popup-${feature.properties.id}-${feature.geometry.type}-${
this.showDataLayerid
}-${this._cleanCount}-${feature.pointRenderingIndex ?? feature.lineRenderingIndex}-${
feature.multiLineStringIndex ?? ""
}`
popup.setContent(
`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading</div>`
)
const createpopup = this.createPopup
leafletLayer.on("popupopen", () => {
if (infobox === undefined) {
const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(feature.properties);
infobox = createpopup(tags, layer);
const tags =
this.allElements?.getEventSourceById(feature.properties.id) ??
new UIEventSource<any>(feature.properties)
infobox = createpopup(tags, layer)
infobox.isShown.addCallback(isShown => {
infobox.isShown.addCallback((isShown) => {
if (!isShown) {
leafletLayer.closePopup()
}
});
})
}
infobox.AttachTo(id)
infobox.Activate();
infobox.Activate()
this.unregister.push(() => {
console.log("Destroying infobox")
infobox.Destroy();
infobox.Destroy()
})
if (this._selectedElement?.data?.properties?.id !== feature.properties.id) {
this._selectedElement?.setData(feature)
}
});
})
// Add the feature to the index to open the popup when needed
this.leafletLayersPerId.set(feature.properties.id + feature.geometry.type, {
feature: feature,
leafletlayer: leafletLayer
leafletlayer: leafletLayer,
})
}
}
}

View file

@ -1,15 +1,15 @@
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {ElementStorage} from "../../Logic/ElementStorage";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import FeatureSource from "../../Logic/FeatureSource/FeatureSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { ElementStorage } from "../../Logic/ElementStorage"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
export interface ShowDataLayerOptions {
features: FeatureSource,
selectedElement?: UIEventSource<any>,
leafletMap: Store<L.Map>,
popup?: undefined | ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen),
zoomToFeatures?: false | boolean,
doShowLayer?: Store<boolean>,
features: FeatureSource
selectedElement?: UIEventSource<any>
leafletMap: Store<L.Map>
popup?: undefined | ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen)
zoomToFeatures?: false | boolean
doShowLayer?: Store<boolean>
state?: { allElements?: ElementStorage }
}
}

View file

@ -1,24 +1,25 @@
/**
* SHows geojson on the given leaflet map, but attempts to figure out the correct layer first
*/
import {Store} from "../../Logic/UIEventSource";
import ShowDataLayer from "./ShowDataLayer";
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
import FilteredLayer from "../../Models/FilteredLayer";
import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
import { Store } from "../../Logic/UIEventSource"
import ShowDataLayer from "./ShowDataLayer"
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import FilteredLayer from "../../Models/FilteredLayer"
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
export default class ShowDataMultiLayer {
constructor(options: ShowDataLayerOptions & { layers: Store<FilteredLayer[]> }) {
new PerLayerFeatureSourceSplitter(options.layers, (perLayer => {
new PerLayerFeatureSourceSplitter(
options.layers,
(perLayer) => {
const newOptions = {
...options,
layerToShow: perLayer.layer.layerDef,
features: perLayer
features: perLayer,
}
new ShowDataLayer(newOptions)
}),
options.features)
},
options.features
)
}
}
}

View file

@ -1,18 +1,21 @@
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"
import { UIEventSource } from "../../Logic/UIEventSource"
export default class ShowOverlayLayer {
public static implementation: (
config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown?: UIEventSource<boolean>
) => void
public static implementation: (config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown?: UIEventSource<boolean>) => void;
constructor(config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined) {
constructor(
config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined
) {
if (ShowOverlayLayer.implementation === undefined) {
throw "Call ShowOverlayLayerImplemenation.initialize() first before using this"
}
ShowOverlayLayer.implementation(config, leafletMap, isShown)
}
}
}

View file

@ -1,45 +1,42 @@
import * as L from "leaflet";
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import ShowOverlayLayer from "./ShowOverlayLayer";
import * as L from "leaflet"
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"
import { UIEventSource } from "../../Logic/UIEventSource"
import ShowOverlayLayer from "./ShowOverlayLayer"
export default class ShowOverlayLayerImplementation {
public static Implement() {
ShowOverlayLayer.implementation = ShowOverlayLayerImplementation.AddToMap
}
public static AddToMap(config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined) {
leafletMap.map(leaflet => {
public static AddToMap(
config: TilesourceConfig,
leafletMap: UIEventSource<any>,
isShown: UIEventSource<boolean> = undefined
) {
leafletMap.map((leaflet) => {
if (leaflet === undefined) {
return;
return
}
const tileLayer = L.tileLayer(config.source,
{
attribution: "",
maxZoom: config.maxzoom,
minZoom: config.minzoom,
// @ts-ignore
wmts: false,
});
const tileLayer = L.tileLayer(config.source, {
attribution: "",
maxZoom: config.maxzoom,
minZoom: config.minzoom,
// @ts-ignore
wmts: false,
})
if (isShown === undefined) {
tileLayer.addTo(leaflet)
}
isShown?.addCallbackAndRunD(isShown => {
isShown?.addCallbackAndRunD((isShown) => {
if (isShown) {
tileLayer.addTo(leaflet)
} else {
leaflet.removeLayer(tileLayer)
}
})
})
}
}
}

View file

@ -1,54 +1,55 @@
import FeatureSource, {Tiled} from "../../Logic/FeatureSource/FeatureSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import ShowDataLayer from "./ShowDataLayer";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import {GeoOperations} from "../../Logic/GeoOperations";
import {Tiles} from "../../Models/TileRange";
import FeatureSource, { Tiled } from "../../Logic/FeatureSource/FeatureSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ShowDataLayer from "./ShowDataLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { GeoOperations } from "../../Logic/GeoOperations"
import { Tiles } from "../../Models/TileRange"
import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json"
import State from "../../State";
import State from "../../State"
export default class ShowTileInfo {
public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true)
constructor(options: {
source: FeatureSource & Tiled, leafletMap: UIEventSource<any>, layer?: LayerConfig,
source: FeatureSource & Tiled
leafletMap: UIEventSource<any>
layer?: LayerConfig
doShowLayer?: UIEventSource<boolean>
}) {
const source = options.source
const metaFeature: Store<{feature, freshness: Date}[]> =
source.features.map(features => {
const metaFeature: Store<{ feature; freshness: Date }[]> = source.features.map(
(features) => {
const bbox = source.bbox
const [z, x, y] = Tiles.tile_from_index(source.tileIndex)
const box = {
"type": "Feature",
"properties": {
"z": z,
"x": x,
"y": y,
"tileIndex": source.tileIndex,
"source": source.name,
"count": features.length,
tileId: source.name + "/" + source.tileIndex
type: "Feature",
properties: {
z: z,
x: x,
y: y,
tileIndex: source.tileIndex,
source: source.name,
count: features.length,
tileId: source.name + "/" + source.tileIndex,
},
"geometry": {
"type": "Polygon",
"coordinates": [
geometry: {
type: "Polygon",
coordinates: [
[
[bbox.minLon, bbox.minLat],
[bbox.minLon, bbox.maxLat],
[bbox.maxLon, bbox.maxLat],
[bbox.maxLon, bbox.minLat],
[bbox.minLon, bbox.minLat]
]
]
}
[bbox.minLon, bbox.minLat],
],
],
},
}
const center = GeoOperations.centerpoint(box)
return [box, center].map(feature => ({feature, freshness: new Date()}))
})
return [box, center].map((feature) => ({ feature, freshness: new Date() }))
}
)
new ShowDataLayer({
layerToShow: ShowTileInfo.styling,
@ -57,7 +58,5 @@ export default class ShowTileInfo {
doShowLayer: options.doShowLayer,
state: State.state,
})
}
}
}

View file

@ -1,10 +1,13 @@
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../../Logic/FeatureSource/FeatureSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Tiles} from "../../Models/TileRange";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import {BBox} from "../../Logic/BBox";
import FilteredLayer from "../../Models/FilteredLayer";
import FeatureSource, {
FeatureSourceForLayer,
Tiled,
} from "../../Logic/FeatureSource/FeatureSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Tiles } from "../../Models/TileRange"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { BBox } from "../../Logic/BBox"
import FilteredLayer from "../../Models/FilteredLayer"
/**
* A feature source containing but a single feature, which keeps stats about a tile
@ -14,32 +17,50 @@ export class TileHierarchyAggregator implements FeatureSource {
public totalValue: number = 0
public showCount: number = 0
public hiddenCount: number = 0
public readonly features = new UIEventSource<{ feature: any, freshness: Date }[]>(TileHierarchyAggregator.empty)
public readonly name;
private _parent: TileHierarchyAggregator;
private _root: TileHierarchyAggregator;
private _z: number;
private _x: number;
private _y: number;
public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>(
TileHierarchyAggregator.empty
)
public readonly name
private _parent: TileHierarchyAggregator
private _root: TileHierarchyAggregator
private _z: number
private _x: number
private _y: number
private _tileIndex: number
private _counter: SingleTileCounter
private _subtiles: [TileHierarchyAggregator, TileHierarchyAggregator, TileHierarchyAggregator, TileHierarchyAggregator] = [undefined, undefined, undefined, undefined]
private _subtiles: [
TileHierarchyAggregator,
TileHierarchyAggregator,
TileHierarchyAggregator,
TileHierarchyAggregator
] = [undefined, undefined, undefined, undefined]
private readonly featuresStatic = []
private readonly featureProperties: { count: string, kilocount: string, tileId: string, id: string, showCount: string, totalCount: string };
private readonly _state: { filteredLayers: UIEventSource<FilteredLayer[]> };
private readonly featureProperties: {
count: string
kilocount: string
tileId: string
id: string
showCount: string
totalCount: string
}
private readonly _state: { filteredLayers: UIEventSource<FilteredLayer[]> }
private readonly updateSignal = new UIEventSource<any>(undefined)
private constructor(parent: TileHierarchyAggregator,
state: {
filteredLayers: UIEventSource<FilteredLayer[]>
},
z: number, x: number, y: number) {
this._parent = parent;
this._state = state;
private constructor(
parent: TileHierarchyAggregator,
state: {
filteredLayers: UIEventSource<FilteredLayer[]>
},
z: number,
x: number,
y: number
) {
this._parent = parent
this._state = state
this._root = parent?._root ?? this
this._z = z;
this._x = x;
this._y = y;
this._z = z
this._x = x
this._y = y
this._tileIndex = Tiles.tile_index(z, x, y)
this.name = "Count(" + this._tileIndex + ")"
@ -49,39 +70,39 @@ export class TileHierarchyAggregator implements FeatureSource {
count: `0`,
kilocount: "0",
showCount: "0",
totalCount: "0"
totalCount: "0",
}
this.featureProperties = totals
const now = new Date()
const feature = {
"type": "Feature",
"properties": totals,
"geometry": {
"type": "Point",
"coordinates": Tiles.centerPointOf(z, x, y)
}
type: "Feature",
properties: totals,
geometry: {
type: "Point",
coordinates: Tiles.centerPointOf(z, x, y),
},
}
this.featuresStatic.push({feature: feature, freshness: now})
this.featuresStatic.push({ feature: feature, freshness: now })
const bbox = BBox.fromTile(z, x, y)
const box = {
"type": "Feature",
"properties": totals,
"geometry": {
"type": "Polygon",
"coordinates": [
type: "Feature",
properties: totals,
geometry: {
type: "Polygon",
coordinates: [
[
[bbox.minLon, bbox.minLat],
[bbox.minLon, bbox.maxLat],
[bbox.maxLon, bbox.maxLat],
[bbox.maxLon, bbox.minLat],
[bbox.minLon, bbox.minLat]
]
]
}
[bbox.minLon, bbox.minLat],
],
],
},
}
this.featuresStatic.push({feature: box, freshness: now})
this.featuresStatic.push({ feature: box, freshness: now })
}
public static createHierarchy(state: { filteredLayers: UIEventSource<FilteredLayer[]> }) {
@ -90,7 +111,7 @@ export class TileHierarchyAggregator implements FeatureSource {
public getTile(tileIndex): TileHierarchyAggregator {
if (tileIndex === this._tileIndex) {
return this;
return this
}
let [tileZ, tileX, tileY] = Tiles.tile_from_index(tileIndex)
while (tileZ - 1 > this._z) {
@ -98,22 +119,21 @@ export class TileHierarchyAggregator implements FeatureSource {
tileY = Math.floor(tileY / 2)
tileZ--
}
const xDiff = tileX - (2 * this._x)
const yDiff = tileY - (2 * this._y)
const subtileIndex = yDiff * 2 + xDiff;
const xDiff = tileX - 2 * this._x
const yDiff = tileY - 2 * this._y
const subtileIndex = yDiff * 2 + xDiff
return this._subtiles[subtileIndex]?.getTile(tileIndex)
}
public addTile(source: FeatureSourceForLayer & Tiled) {
const self = this;
const self = this
if (source.tileIndex === this._tileIndex) {
if (this._counter === undefined) {
this._counter = new SingleTileCounter(this._tileIndex)
this._counter.countsPerLayer.addCallbackAndRun(_ => self.update())
this._counter.countsPerLayer.addCallbackAndRun((_) => self.update())
}
this._counter.addTileCount(source)
} else {
// We have to give it to one of the subtiles
let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex)
while (tileZ - 1 > this._z) {
@ -121,42 +141,57 @@ export class TileHierarchyAggregator implements FeatureSource {
tileY = Math.floor(tileY / 2)
tileZ--
}
const xDiff = tileX - (2 * this._x)
const yDiff = tileY - (2 * this._y)
const xDiff = tileX - 2 * this._x
const yDiff = tileY - 2 * this._y
const subtileIndex = yDiff * 2 + xDiff;
const subtileIndex = yDiff * 2 + xDiff
if (this._subtiles[subtileIndex] === undefined) {
this._subtiles[subtileIndex] = new TileHierarchyAggregator(this, this._state, tileZ, tileX, tileY)
this._subtiles[subtileIndex] = new TileHierarchyAggregator(
this,
this._state,
tileZ,
tileX,
tileY
)
}
this._subtiles[subtileIndex].addTile(source)
}
this.updateSignal.setData(source)
}
getCountsForZoom(clusteringConfig: { maxZoom: number }, locationControl: UIEventSource<{ zoom: number }>, cutoff: number = 0): FeatureSource {
getCountsForZoom(
clusteringConfig: { maxZoom: number },
locationControl: UIEventSource<{ zoom: number }>,
cutoff: number = 0
): FeatureSource {
const self = this
const empty = []
const features = locationControl.map(loc => loc.zoom).map(targetZoom => {
if (targetZoom - 1 > clusteringConfig.maxZoom) {
return empty
}
const features = locationControl
.map((loc) => loc.zoom)
.map(
(targetZoom) => {
if (targetZoom - 1 > clusteringConfig.maxZoom) {
return empty
}
const features: {feature: any, freshness: Date}[] = []
self.visitSubTiles(aggr => {
if (aggr.showCount < cutoff) {
return false
}
if (aggr._z === targetZoom) {
features.push(...aggr.features.data)
return false
}
return aggr._z <= targetZoom;
})
const features: { feature: any; freshness: Date }[] = []
self.visitSubTiles((aggr) => {
if (aggr.showCount < cutoff) {
return false
}
if (aggr._z === targetZoom) {
features.push(...aggr.features.data)
return false
}
return aggr._z <= targetZoom
})
return features
}, [this.updateSignal.stabilized(500)])
return features
},
[this.updateSignal.stabilized(500)]
)
return new StaticFeatureSource(features);
return new StaticFeatureSource(features)
}
private update() {
@ -176,14 +211,13 @@ export class TileHierarchyAggregator implements FeatureSource {
if (flayer.isDisplayed.data && this._z >= flayer.layerDef.minzoom) {
showCount += count
} else {
hiddenCount += count;
hiddenCount += count
}
})
for (const tile of this._subtiles) {
if (tile === undefined) {
continue;
continue
}
total += tile.totalValue
@ -192,7 +226,10 @@ export class TileHierarchyAggregator implements FeatureSource {
for (const key in tile.featureProperties) {
if (key.startsWith("layer:")) {
newMap.set(key, (newMap.get(key) ?? 0) + Number(tile.featureProperties[key] ?? 0))
newMap.set(
key,
(newMap.get(key) ?? 0) + Number(tile.featureProperties[key] ?? 0)
)
}
}
}
@ -205,8 +242,8 @@ export class TileHierarchyAggregator implements FeatureSource {
if (total === 0) {
this.features.setData(TileHierarchyAggregator.empty)
} else {
this.featureProperties.count = "" + total;
this.featureProperties.kilocount = "" + Math.floor(total / 1000);
this.featureProperties.count = "" + total
this.featureProperties.kilocount = "" + Math.floor(total / 1000)
this.featureProperties.showCount = "" + showCount
this.featureProperties.totalCount = "" + total
newMap.forEach((value, key) => {
@ -221,7 +258,7 @@ export class TileHierarchyAggregator implements FeatureSource {
private visitSubTiles(f: (aggr: TileHierarchyAggregator) => boolean) {
const visitFurther = f(this)
if (visitFurther) {
this._subtiles.forEach(tile => tile?.visitSubTiles(f))
this._subtiles.forEach((tile) => tile?.visitSubTiles(f))
}
}
}
@ -230,20 +267,22 @@ export class TileHierarchyAggregator implements FeatureSource {
* Keeps track of a single tile
*/
class SingleTileCounter implements Tiled {
public readonly bbox: BBox;
public readonly tileIndex: number;
public readonly countsPerLayer: UIEventSource<Map<string, number>> = new UIEventSource<Map<string, number>>(new Map<string, number>())
public readonly bbox: BBox
public readonly tileIndex: number
public readonly countsPerLayer: UIEventSource<Map<string, number>> = new UIEventSource<
Map<string, number>
>(new Map<string, number>())
public readonly z: number
public readonly x: number
public readonly y: number
private readonly registeredLayers: Map<string, LayerConfig> = new Map<string, LayerConfig>();
private readonly registeredLayers: Map<string, LayerConfig> = new Map<string, LayerConfig>()
constructor(tileIndex: number) {
this.tileIndex = tileIndex
this.bbox = BBox.fromTileIndex(tileIndex)
const [z, x, y] = Tiles.tile_from_index(tileIndex)
this.z = z;
this.x = x;
this.z = z
this.x = x
this.y = y
}
@ -251,11 +290,13 @@ class SingleTileCounter implements Tiled {
const layer = source.layer.layerDef
this.registeredLayers.set(layer.id, layer)
const self = this
source.features.map(f => {
const isDisplayed = source.layer.isDisplayed.data
self.countsPerLayer.data.set(layer.id, isDisplayed ? f.length : 0)
self.countsPerLayer.ping()
}, [source.layer.isDisplayed])
source.features.map(
(f) => {
const isDisplayed = source.layer.isDisplayed.data
self.countsPerLayer.data.set(layer.id, isDisplayed ? f.length : 0)
self.countsPerLayer.ping()
},
[source.layer.isDisplayed]
)
}
}
}