forked from MapComplete/MapComplete
Feature: first version of clustering at low zoom levels, filters don't update yet (WIP)
This commit is contained in:
parent
4e033a93a5
commit
8360ab9a8b
11 changed files with 562 additions and 262 deletions
|
@ -0,0 +1,87 @@
|
|||
import { FeatureSource } from "../FeatureSource"
|
||||
import { Feature, Point } from "geojson"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import StaticFeatureSource from "../Sources/StaticFeatureSource"
|
||||
import { Tiles } from "../../../Models/TileRange"
|
||||
|
||||
export interface ClusteringOptions {
|
||||
/**
|
||||
* If the zoomlevel is (strictly) above the specified value, don't cluster no matter how many features the tile contains.
|
||||
*/
|
||||
dontClusterAboveZoom?: number
|
||||
/**
|
||||
* If the number of features in a _tile_ is equal or more then this number,
|
||||
* drop those features and emit a summary tile instead
|
||||
*/
|
||||
cutoff?: 20 | number
|
||||
}
|
||||
|
||||
export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> implements FeatureSource<T> {
|
||||
|
||||
public readonly summaryPoints: FeatureSource
|
||||
private readonly id: string
|
||||
features: Store<T[]>
|
||||
|
||||
/**
|
||||
*The clustering feature source works _only_ on points and is a preprocessing step for the ShowDataLayer.
|
||||
* If a tile contains many points, a 'summary' point is emitted instead in 'summaryPoints'.
|
||||
* The points from the summary will _not_ be emitted in 'this.features' in that case.
|
||||
*
|
||||
* We ignore the polygons, as polygons get smaller when zoomed out and thus don't clutter the map too much
|
||||
*/
|
||||
constructor(upstream: FeatureSource<T>,
|
||||
currentZoomlevel: Store<number>,
|
||||
id: string,
|
||||
options?: ClusteringOptions) {
|
||||
this.id = id
|
||||
const clusterCutoff = options?.dontClusterAboveZoom ?? 17
|
||||
const doCluster = options?.dontClusterAboveZoom === undefined ? new ImmutableStore(true) : currentZoomlevel.map(zoom => zoom <= clusterCutoff)
|
||||
const cutoff = options?.cutoff ?? 20
|
||||
const summaryPoints = new UIEventSource<Feature<Point>[]>([])
|
||||
currentZoomlevel = currentZoomlevel.stabilized(500)
|
||||
this.summaryPoints = new StaticFeatureSource(summaryPoints)
|
||||
this.features = (upstream.features.map(features => {
|
||||
if (!doCluster.data) {
|
||||
summaryPoints.set([])
|
||||
return features
|
||||
}
|
||||
|
||||
const z = currentZoomlevel.data
|
||||
const perTile = GeoOperations.spreadIntoBboxes(features, z)
|
||||
const resultingFeatures = []
|
||||
const summary: Feature<Point>[] = []
|
||||
for (const tileIndex of perTile.keys()) {
|
||||
const tileFeatures: Feature<Point>[] = perTile.get(tileIndex)
|
||||
if (tileFeatures.length > cutoff) {
|
||||
summary.push(this.createSummaryFeature(tileFeatures, tileIndex))
|
||||
} else {
|
||||
resultingFeatures.push(...tileFeatures)
|
||||
}
|
||||
}
|
||||
summaryPoints.set(summary)
|
||||
return resultingFeatures
|
||||
|
||||
}, [doCluster, currentZoomlevel]))
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private createSummaryFeature(features: Feature<Point>[], tileId: number): Feature<Point> {
|
||||
const [z, x, y] = Tiles.tile_from_index(tileId)
|
||||
const [lon, lat] = Tiles.centerPointOf(z, x, y)
|
||||
return <Feature<Point>>{
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [lon, lat]
|
||||
},
|
||||
properties: {
|
||||
id: "summary_" + this.id + "_" + tileId,
|
||||
z,
|
||||
total_metric: "" + features.length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue