76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
import { useEffect, useMemo, useReducer } from "react"
|
|
import Supercluster, { type ClusterProperties } from "supercluster"
|
|
|
|
import { useMapViewport } from "./use-map-viewport"
|
|
|
|
import type { FeatureCollection, GeoJsonProperties, Point } from "geojson"
|
|
|
|
import type { CityMarkerProperties } from "@/types/components/maps/destinationMarkers"
|
|
|
|
export function useSupercluster<T extends GeoJsonProperties>(
|
|
geojson: FeatureCollection<Point, T>,
|
|
superclusterOptions: Supercluster.Options<T, ClusterProperties>
|
|
) {
|
|
// create the clusterer and keep it
|
|
const clusterer = useMemo(() => {
|
|
return new Supercluster(superclusterOptions)
|
|
}, [superclusterOptions])
|
|
|
|
// version-number for the data loaded into the clusterer
|
|
// (this is needed to trigger updating the clusters when data was changed)
|
|
const [version, dataWasUpdated] = useReducer((x: number) => x + 1, 0)
|
|
|
|
// when data changes, load it into the clusterer
|
|
useEffect(() => {
|
|
clusterer.load(geojson.features)
|
|
dataWasUpdated()
|
|
}, [clusterer, geojson])
|
|
|
|
// get bounding-box and zoomlevel from the map
|
|
const { bbox, zoom } = useMapViewport({ padding: 100 })
|
|
|
|
// retrieve the clusters within the current viewport
|
|
const clusters = useMemo(() => {
|
|
// don't try to read clusters before data was loaded into the clusterer (version===0),
|
|
// otherwise getClusters will crash
|
|
if (!clusterer || version === 0) return []
|
|
|
|
return clusterer.getClusters(bbox, zoom)
|
|
}, [version, clusterer, bbox, zoom])
|
|
|
|
function getClusterZoom(clusterId: number) {
|
|
if (!clusterer || version === 0) {
|
|
return null
|
|
}
|
|
|
|
return clusterer.getClusterExpansionZoom(clusterId)
|
|
}
|
|
|
|
// retrieve the hotel ids included in the cluster
|
|
const containedHotelIds = clusters.map((cluster) => {
|
|
if (cluster.properties?.cluster && typeof cluster.id === "number") {
|
|
return clusterer
|
|
.getLeaves(cluster.id)
|
|
.map((location) => String(location.id))
|
|
}
|
|
return []
|
|
})
|
|
|
|
// retrieve the city ids included in the cluster
|
|
const containedCityIds = clusters.map((cluster) => {
|
|
if (cluster.properties?.cluster && typeof cluster.id === "number") {
|
|
return clusterer
|
|
.getLeaves(cluster.id)
|
|
.map((city) => city.properties as CityMarkerProperties)
|
|
}
|
|
return []
|
|
})
|
|
|
|
return {
|
|
clusters,
|
|
containedHotelIds,
|
|
containedCityIds,
|
|
getClusterZoom,
|
|
}
|
|
}
|