Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce minPoints option #156

Merged
merged 2 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Returns the zoom on which the cluster expands into several children (useful for
|------------|---------|-------------------------------------------------------------------|
| minZoom | 0 | Minimum zoom level at which clusters are generated. |
| maxZoom | 16 | Maximum zoom level at which clusters are generated. |
| minPoints | 2 | Minimum number of points to form a cluster. |
| radius | 40 | Cluster radius, in pixels. |
| extent | 512 | (Tiles) Tile extent. Radius is calculated relative to this value. |
| nodeSize | 64 | Size of the KD-tree leaf node. Affects performance. |
Expand Down
65 changes: 42 additions & 23 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import KDBush from 'kdbush';
const defaultOptions = {
minZoom: 0, // min zoom to generate clusters on
maxZoom: 16, // max zoom level to cluster the points on
minPoints: 2, // minimum points to form a cluster
radius: 40, // cluster radius in pixels
extent: 512, // tile extent (radius is calculated relative to it)
nodeSize: 64, // size of the KD-tree leaf node, affects performance
Expand Down Expand Up @@ -229,7 +230,7 @@ export default class Supercluster {

_cluster(points, zoom) {
const clusters = [];
const {radius, extent, reduce} = this.options;
const {radius, extent, reduce, minPoints} = this.options;
const r = radius / (extent * Math.pow(2, zoom));

// loop through each point
Expand All @@ -243,39 +244,57 @@ export default class Supercluster {
const tree = this.trees[zoom + 1];
const neighborIds = tree.within(p.x, p.y, r);

let numPoints = p.numPoints || 1;
let wx = p.x * numPoints;
let wy = p.y * numPoints;

let clusterProperties = reduce && numPoints > 1 ? this._map(p, true) : null;

// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = (i << 5) + (zoom + 1) + this.points.length;
const numPointsOrigin = p.numPoints || 1;
let numPoints = numPointsOrigin;

// count the number of points in a potential cluster
for (const neighborId of neighborIds) {
const b = tree.points[neighborId];
// filter out neighbors that are already processed
if (b.zoom <= zoom) continue;
b.zoom = zoom; // save the zoom (so it doesn't get processed twice)
if (b.zoom > zoom) numPoints += b.numPoints || 1;
}

if (numPoints >= minPoints) { // enough points to form a cluster
let wx = p.x * numPointsOrigin;
let wy = p.y * numPointsOrigin;

let clusterProperties = reduce && numPointsOrigin > 1 ? this._map(p, true) : null;

// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = (i << 5) + (zoom + 1) + this.points.length;

for (const neighborId of neighborIds) {
const b = tree.points[neighborId];

if (b.zoom <= zoom) continue;
b.zoom = zoom; // save the zoom (so it doesn't get processed twice)

const numPoints2 = b.numPoints || 1;
wx += b.x * numPoints2; // accumulate coordinates for calculating weighted center
wy += b.y * numPoints2;
const numPoints2 = b.numPoints || 1;
wx += b.x * numPoints2; // accumulate coordinates for calculating weighted center
wy += b.y * numPoints2;

numPoints += numPoints2;
b.parentId = id;
b.parentId = id;

if (reduce) {
if (!clusterProperties) clusterProperties = this._map(p, true);
reduce(clusterProperties, this._map(b));
if (reduce) {
if (!clusterProperties) clusterProperties = this._map(p, true);
reduce(clusterProperties, this._map(b));
}
}
}

if (numPoints === 1) {
clusters.push(p);
} else {
p.parentId = id;
clusters.push(createCluster(wx / numPoints, wy / numPoints, id, numPoints, clusterProperties));

} else { // left points as unclustered
clusters.push(p);

if (numPoints > 1) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious why we compare numPoints with 1 instead of numPointsOrigin?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When numPointsOrigin is bigger than 1, it means that the origin is itself a cluster, which could only be formed from minPoints points or more. So in case of origin cluster, we never reach this else case (numPoints will always be >= minPoints).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see! Thank you!

for (const neighborId of neighborIds) {
const b = tree.points[neighborId];
if (b.zoom <= zoom) continue;
b.zoom = zoom;
clusters.push(b);
}
}
}
}

Expand Down
Loading