UNPKG

react-map-gl-supercluster

Version:

73 lines (72 loc) 3.41 kB
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 }; } }