react-map-gl-supercluster
Version:
73 lines (72 loc) • 3.41 kB
JavaScript
import { useEffect, useMemo, useRef, useState } from 'react';
import Supercluster from 'supercluster';
import { getMapState, isEqual, isClustersShallowEqual } from './utils.js';
export function create(useMap) {
return function useReactMapGLSupercluster(points, options = {}) {
const map = useResolvedMapRef(options.mapRef);
const supercluster = useSuperclusterFactory(points, options);
const [state, setState] = useState(() => {
const mapState = map != null ? getMapState(map) : null;
return {
supercluster,
mapState,
clusters: mapState != null ? supercluster.getClusters(mapState.bounds, mapState.zoom) : [],
};
});
useEffect(() => {
// Map is not available yet, no clusters
if (map == null) {
setState({ supercluster, clusters: [], mapState: null });
return;
}
const update = () => {
setState((current) => {
const nextMapState = getMapState(map);
// Supercluster has changed, always re-ask clusters
if (current.supercluster !== supercluster) {
const nextClusters = supercluster.getClusters(nextMapState.bounds, nextMapState.zoom);
return { supercluster, mapState: nextMapState, clusters: nextClusters };
}
// Map state has changed, but points stay the same
if (!isEqual(current.mapState, nextMapState)) {
const nextClusters = supercluster.getClusters(nextMapState.bounds, nextMapState.zoom);
// Clusters might stay the same, avoid redundant re-renders
if (!isClustersShallowEqual(current.clusters, nextClusters))
return { supercluster, mapState: nextMapState, clusters: nextClusters };
}
return current;
});
};
// Perform update because the map state might have changed, then subscribe to interactions
update();
map.on('move', update);
return () => {
map.off('move', update);
};
}, [map, supercluster]);
return state;
};
function useResolvedMapRef(outerRef) {
const maps = useMap();
if (outerRef != null)
return 'current' in outerRef ? outerRef.current ?? null : outerRef;
return maps.current ?? null;
}
function useSuperclusterFactory(points, _options) {
// Memoize options
const nextOptions = pickOptions(_options);
const optionsRef = useRef(nextOptions);
if (!isEqual(optionsRef.current, nextOptions))
optionsRef.current = nextOptions;
const options = optionsRef.current;
return useMemo(() => {
const instance = new Supercluster(options);
instance.load(points);
return instance;
}, [points, options]);
}
function pickOptions(options) {
const { minZoom = 0, maxZoom = 16, radius = 40, minPoints = 2, extent = 512, nodeSize = 64, generateId = false, map, reduce, } = options;
return { minZoom, maxZoom, radius, minPoints, extent, nodeSize, generateId, map, reduce };
}
}