react-map-gl-supercluster
TypeScript icon, indicating that this package has built-in type declarations

2.0.2 • Public • Published

react-map-gl-supercluster

The easiest way to get react-map-gl and supercluster to work together

Highlights

  • TypeScript support.
  • ESM support.
  • Ready for tree shaking.
  • No unnecessary re-renders.

Install

$ yarn add react-map-gl-supercluster
# or
$ npm install react-map-gl-supercluster

Usage

import Map, { useMap } from 'react-map-gl' // react-map-gl/maplibre if you use maplibre instead
import {
  useSupercluster,
  PointFeature,
  PointFeatureProperties,
  PointClusterProperties
} from 'react-map-gl-supercluster' // or react-map-gl-supercluster/maplibre if you use maplibre instead

type Item = {}
type ItemPointFeatureProperties = PointFeatureProperties<{ item: Item }>
type ItemPointClusterProperties = PointClusterProperties<{ items: Item[] }>

function MyAwesomeMap(): ReactElement {
  const mapRef = useRef()

  // Points should be memoized
  const points = useMemo(() => createPoints(items), [])

  const { supercluster, clusters } = useSupercluster(points, {
    mapRef,
    map: mapFeature,
    reduce: reduceCluster
  })

  const expandCluster = (clusterId, coordinates) => {
    const zoom = supercluster.getClusterExpansionZoom(clusterId)
    mapRef.current?.easeTo({
      center: [coordinates.longitude, coordinates.latitude],
      zoom,
    })
  }

  return (
    <Map ref={mapRef}>
      {clusters.map((cluster) => {
        const [longitude, latitude] = cluster.geometry.coordinates

        return cluster.properties.cluster ? (
          <ClusterMarker
            key={`cluster-${cluster.properties.cluster_id}`}
            longitude={longitude}
            latitude={latitude}
            onClick={() => expandCluster(cluster.properties.cluster_id, { longitude, latitude })}
          />
        ) : (
          <Marker key={`item-${cluster.properties.item.id}`} longitude={longitude} latitude={longitude} />
        )
      })}
    </Map>
  )
}

function createPoints(items: Item[]): Array<PointFeature<ItemPointFeatureProperties>> {
  return items.map(createPoint)
}

function createPoint(item: Item): PointFeature<ItemPointFeatureProperties> {
  const { longitude, latitude } = item

  return {
    type: 'Feature',
    properties: { cluster: false, item },
    geometry: {
      type: 'Point',
      coordinates: [longitude, latitude],
    },
  }
}

// It creates cluster properties from feature properties.
function mapFeature(props: ItemPointFeatureProperties): ItemPointClusterProperties {
  return { items: [props.item] }
}

// It merges clusters properties. Yes, it's simply mutates.
function reduceCluster(memo: ItemPointClusterProperties, props: ItemPointClusterProperties): void {
  memo.items = memo.items.concat(props.items)
}

Alternatively you can use the hook inside Map.

import Map, { useMap } from 'react-map-gl'// react-map-gl/maplibre if you use maplibre instead
import { useSupercluster } from 'react-map-gl-supercluster' // or react-map-gl-supercluster/maplibre if you use maplibre instead

function MyAwesomeMap(): ReactElement {
  return (
    <Map>
      <Markers items={items} />
    </Map>
  )
}

type MarkersProps = {
  items: Item[]
}

function Markers(props: MarkersProps): ReactElement {
  const { items } = props

  const map = useMap().current

  // Points should be memoized
  const points = useMemo(() => createPoints(items), [items])

  const { supercluster, clusters } = useSupercluster(points, {
    map: mapFeature,
    reduce: reduceCluster
  })

  const expandCluster = (clusterId, coordinates) => {
    const zoom = supercluster.getClusterExpansionZoom(clusterId)
    map?.easeTo({
      center: [coordinates.longitude, coordinates.latitude],
      zoom,
    })
  }

  return (
    <>
      {clusters.map((cluster) => {
        const [longitude, latitude] = cluster.geometry.coordinates

        return cluster.properties.cluster ? (
          <ClusterMarker
            key={`cluster-${cluster.properties.cluster_id}`}
            longitude={longitude}
            latitude={latitude}
            onClick={() => expandCluster(cluster.properties.cluster_id, { longitude, latitude })}
          />
        ) : (
          <Marker key={`item-${cluster.properties.item.id}`} longitude={longitude} latitude={longitude} />
        )
      })}
    </>
  )
}

API

useSupercluster

The hook can be used in a component which renders Map component or inside Map children.

Arguments

  1. points - GeoJSON points array. The value should be memoized.
  2. options – various options, see bellow.

Return value

Object which contains 2 fields:

  • clusters – clusters list
  • supercluster – supercluster instance.

Options

Option Default Description
mapRef Optional Reference to react-map-gl instance.
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.
map Optional A function that returns cluster properties corresponding to a single point. Should be memoized. See.
reduce Optional A reduce function that merges properties of two clusters into one. Should be memoized. See.

react-map-gl-supercluster supports all supercluster options, you can find more information about them there.

FAQ

Does it support react-map-gl v5/v6?

No, it doesn't.

Why does it cause component re-rendering or why do I get infinite component update loop?

Please be careful with points and map/reduce functions. They always should be memoized.

Does it support WebWorker?

No, the hook is running in the main thread. But probably WebWorker support will come in the future.

Package Sidebar

Install

npm i react-map-gl-supercluster

Weekly Downloads

167

Version

2.0.2

License

MIT

Unpacked Size

20.5 kB

Total Files

17

Last publish

Collaborators

  • khmm12