-
Notifications
You must be signed in to change notification settings - Fork 0
/
ClusterManager.ts
129 lines (118 loc) · 3.49 KB
/
ClusterManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { ClusterGenerator } from './ClusterGenerator'
import { defaultOptions, Options } from './defaults'
import { coordinateToPoint, coordinateToPointBounds } from './tileMath'
import { nodesForRadius } from './utils'
import { Node } from './Node'
import { Coordinate, CoordinateBounds, Point } from './types'
/** The extreme north-west and south-east coordinates of the world. */
export const WORLD_BOUNDS = {
northWest: {
latitude: 90,
longitude: -180
},
southEast: {
latitude: -90,
longitude: 180
}
}
/**
* The cluster manager.
*
* @typeParam T The type of a point
*/
export class ClusterManager<T> {
private clusters: Array<ClusterGenerator<Node<T>>>
private minZoom: number
private maxZoom: number
/**
* Create a cluster manager.
*
* @typeParam T The type of a point
*
* @param points An array of points.
* @param getCoordinate A function to return the coordinate of a point.
* @param pointFactory A function to return the point for a cluster node.
* @param options Options to control the cluster generation.
*/
constructor(
points: T[],
getCoordinate: (data: T) => Coordinate,
pointFactory: (coordinate: Coordinate, nodes: Node<T>[]) => T,
options: Partial<Options> = {}
) {
// Merge the options with the default options.
const { minZoom, maxZoom, minPoints, radius, tileSize, calcDistance } = {
...defaultOptions,
...options
}
this.minZoom = minZoom
this.maxZoom = maxZoom
this.clusters = new Array(maxZoom + 1)
const topLeft = coordinateToPoint(WORLD_BOUNDS.northWest)
const bottomRight = coordinateToPoint(WORLD_BOUNDS.southEast)
const rectangle = {
x: topLeft.x,
y: topLeft.y,
width: bottomRight.x - topLeft.x,
height: bottomRight.y - topLeft.y
}
// Generate a node for each point.
let nodes: Node<T>[] = points.map(datum => {
const coordinate = getCoordinate(datum)
return new Node(
coordinateToPoint(coordinate),
coordinate,
[],
null,
datum
)
})
// Create an initial cluster from all the points.
this.clusters[maxZoom + 1] = new ClusterGenerator(
nodes,
p => p.point,
rectangle,
calcDistance
)
// Zoom in a step at a time calculating a new cluster from the previous.
for (let zoom = maxZoom; zoom >= minZoom; --zoom) {
// Rather than recalculating the points we increase the radius of
// the cluster area.
const zoomRadius = radius / (tileSize * Math.pow(2, zoom))
nodes = nodesForRadius(
this.clusters[zoom + 1],
zoomRadius,
minPoints,
pointFactory
)
// Make the cluster for this zoom level.
this.clusters[zoom] = new ClusterGenerator(
nodes,
p => p.point,
rectangle,
calcDistance
)
}
}
/**
* Gets the cluster data for a particular area and zoom level.
*
* @typeParam T The type of a point.
*
* @param bounds The area for which to return nodes.
* @param zoom The zoom level.
* @returns An array of nodes.
*/
getCluster(bounds: CoordinateBounds, zoom: number): Node<T>[] {
// Get the cluster for the zoom level.
const z = Math.max(
this.minZoom,
Math.min(Math.floor(zoom), this.maxZoom + 1)
)
const cluster = this.clusters[z]
// Get the point bounds from the coordinate bounds.
const pointBounds = coordinateToPointBounds(bounds)
// Get the nodes.
return cluster.range(pointBounds)
}
}