An extension for Mappedin JS that allows clustering markers together.
With NPM:
npm install @mappedin/marker-cluster
With Yarn:
yarn add @mappedin/marker-cluster
import { show3dMap, Marker } from '@mappedin/mappedin-js';
import { MarkerCluster, ClusterFn } from `@mappedin/marker-clustering`;
const mapView = await show3dMap(...);
/**
* Add Markers to the map like normal, keeping track of markers that should be clustered.
*/
const markers: Marker[] = []
mapData.getByType('space').forEach(space => {
const marker = mapView.Markers.add(space, `<div>${space.name}</div>`, {
rank: 'always-visible', // Markers should have rank: 'always-visible' for the best effect
});
markers.push(marker);
});
/**
* Define a cluster function of type ClusterFn that returns a special Marker.
*/
const clusterFn: ClusterFn = ({ center, markers }) => {
return mapView.Markers.add(center, `<div>${markers.length}</div>`, {
rank: 'always-visible', // Cluster markers should also be always-visible
});
};
/**
* Create a new MarkerCluster from the MapView and a cluster function, then add all Markers that
* should be considered for clustering.
*/
const cluster = new MarkerCluster(mapView, clusterFn);
for (const [marker] of markerProperties) {
cluster.add(marker);
}
/**
* Dispose the MarkerCluster when it is no longer needed.
*/
cluster.destroy();
type MarkerClusterOptions<Algorithm extends IClusterAlgorithm = ScreenDistanceClustering> = Partial<{
/**
* The floor the clustered marker is on. If unspecified, the floor of the first added marker will be used.
*/
floor: Floor | null;
/**
* The minimum number of markers in a cluster.
*
* @default 2
*/
minSize: number;
/**
* The debounce time in milliseconds for computing clusters when the camera is moving. If set to 0, the clusters will
* be computed on every camera frame. This could lead to performance issues if there are a lot of markers.
*
* @default 50
*/
debounce: number;
/**
* Whether the original markers should be hidden when clustered together.
*
* @default true
*/
hideClusteredMarkers: boolean;
/**
* A custom algorithm to use for clustering.
*
* @default ScreenDistanceClustering
*/
algorithm: Algorithm;
/**
* Options for the algorithm.
*/
algorithmOptions: OptionsOf<Algorithm>;
}
/**
* The default algorithm used by MarkerCluster is the built-in ScreenDistanceClustering algorithm. When run, this algorithm will cluster markers whose anchor coordinates are within a certain number of pixels on screen.
*/
import { ScreenDistanceClustering } from '@mappedin/marker-cluster';
export type ScreenDistanceClusteringOptions = Partial<{
/**
* The distance in pixels that a marker should be from another marker to be considered part of the same cluster.
*/
threshold: number;
}>;
import { MarkeCluster, IClusterAlgorithm } from '@mappedin/marker-cluster';
/**
* Custom options can be passed to IClusterAlgorithm for stronger typing.
*/
type CustomClusteringOptions = {
threshold: number;
};
/**
* Define a custom clustering algorithm that implements IClusterAlgorithm.
*/
class CustomClustering implements IClusterAlgorithm<CustomClusteringOptions> {
/**
* A custom algorithm should probably be constructed with the MapView, but it is not necessary.
*/
constructor(mapView: MapView) {}
/**
* setOptions() is called immediately after the custom algorithm is instantiated with the value passed to "MarkerClusteringOptions.algorithmOptions". This happens in the MarkerCluster constructor.
*/
setOptions(options: CustomClusteringOptions): void {}
/**
* load() is called whenever the debounce period elapses between Camera moves. It gets passed an array of all markers that were added to the MarkerCluster.
*/
load(markers: Marker[]): void {}
/**
* getClusters() is called whenever MarkerCluster wants to update the rendered clusters. This usually happens immediately after load() is called.
*/
getClusters(): Marker[][] {}
/**
* destroy() is called when "MarkerCluster.destroy()" is called. Any resources that the ClusteringAlgorithm is holding onto can be freed up here.
*/
destroy(): void {}
}
/**
* Create a new MarkerCluster with the custom algorithm.
*/
const cluster = new MarkerCluster(mapView, clusterFn, {
algorithm: new CustomAlgorithm(mapView),
algorithmOptions: { threshold: 100 },
});
import { PointRBush } from '@mappedin/marker-cluster';
/**
* PointRBush is a utility class that extends the rbush package to work with { x, y } points. It can be used to help build custom clustering algorithms, but it is not necessary.
*/
const rbush = new PointRBush();
/**
* Insert, load, remove, or clear points the same as rbush.
*/
const points = [
{ x: 1, y: 2 },
{ x: 3, y: 4 },
{ x: 5, y: 6 },
];
rbush.insert(points[0]);
rbush.load([points[1], points[2]]);
rbush.remove(points[0]);
rbush.clear();
/**
* Search for points with a box the same as rbush.
*/
const results = rbush.search({ minX: 0, maxX: 5, minY: 0, maxY: 5 });
/**
* Peek at an item in the tree. This can be useful when generating clusters by removing any already clustered points and retrieving a remaining point that still needs to be clustered.
*/
const item = rbush.peek();
/**
* Check if a point is in the tree. This can be useful for optimizations if previous clusters are cached between updates.
*/
const exists = rbush.has({ x: 1, y: 2 });
/**
* Retrieve points from the tree. This can be useful for optimizations if previous clusters are cached between updates.
*/
const found = rbushl.find(point => point.x > 2);