Clustered Features

See https://openlayers.org/en/latest/examples/cluster.html

import React, { useState, memo } from "react";
import "ol/ol.css";
import { Style, Circle as CircleStyle, Fill, Stroke, Text } from "ol/style";
import { Map } from "@react-ol/fiber";

export const coordinates = (() => {
  const count = 20000;
  const e = 4500000;
  const coordinates = new Array(count);
  for (let i = 0; i < count; i++) {
    coordinates[i] = [2 * e * Math.random() - e, 2 * e * Math.random() - e];
  }
  return coordinates;
})();

export const styleCache = [];

export const PointsAtCoordinates = memo(({ coordinates }) => {
  // It is important to memoize this, to avoid iterating over all the coordinates
  // on each react render if the array is not changed.
  return coordinates.map((coordinate) => (
    <olFeature key={coordinate.join()}>
      <olGeomPoint args={[coordinate]} />
    </olFeature>
  ));
});

export const ExampleCluster = () => {
  const [distance, setDistance] = useState(40);
  const [minDistance, setMinDistance] = useState(20);
  return (
    <>
      <form>
        <label htmlFor="distance">Cluster distance</label>
        <input
          id="distance"
          type="range"
          min="0"
          max="100"
          step="1"
          value={distance}
          onChange={(e) => setDistance(parseInt(e.target.value, 10))}
        />
        <label htmlFor="minDistance">Minimum distance</label>
        <input
          id="minDistance"
          type="range"
          min="0"
          max="100"
          step="1"
          value={minDistance}
          onChange={(e) => setMinDistance(parseInt(e.target.value, 10))}
        />
      </form>
      <Map>
        <olView initialCenter={[0, 0]} initialZoom={2} />
        <olLayerTile>
          <olSourceOSM />
        </olLayerTile>
        <olLayerVector
          style={(feature) => {
            const size = feature.get("features").length;
            let style = styleCache[size];
            if (!style) {
              style = new Style({
                image: new CircleStyle({
                  radius: 10,
                  stroke: new Stroke({
                    color: "#fff",
                  }),
                  fill: new Fill({
                    color: "#3399CC",
                  }),
                }),
                text: new Text({
                  text: size.toString(),
                  fill: new Fill({
                    color: "#fff",
                  }),
                }),
              });
              styleCache[size] = style;
            }
            return style;
          }}
        >
          <olSourceCluster distance={distance} minDistance={minDistance}>
            <olSourceVector>
              <PointsAtCoordinates coordinates={coordinates} />
            </olSourceVector>
          </olSourceCluster>
        </olLayerVector>
      </Map>
    </>
  );
};