0

I'm looking for a way to cluster polygons using react-leaflet v4 and react-leaflet-markercluster. I have not found any up-to-date examples of how I can achieve this, so I'm hoping I might get some help here.

Any example code to get me started would be a great help!

3
  • Could you explain more of what you want to achieve, do not really get the part with cluster polygons? The problem I see with most react-leaflet extensions is that they are not maintained and hard to adapt to your needs. If you look at the code of most of these extensions its often just 150 lines of code that utilize a real Leaflet plugin. I often end up copying the code from the react-leaflet plugins and make them work. Let me know what you need so I can see if my solution matches
    – Disco
    Commented Oct 26, 2022 at 14:15
  • @Disco Thanks for the response. So with react-leaflet-markercluster, each marker will automatically cluster at specific zoom-levels. It will cluster both markers and circles, but not polygons. I've been using polygons as a type of marker in my map, so I'm looking for a way to cluster these together with normal markers and circles.
    – steinway
    Commented Oct 26, 2022 at 20:41
  • It should be possible, I have not done anything with polygons, but I posted the code I use for markerclusters below. Hopefully it points you in the right direction. Best of luck. And If you find a suitable solution, please post it as an answer.
    – Disco
    Commented Oct 27, 2022 at 9:44

1 Answer 1

1

This will probably not solve your problem directly but hopefully show that using markercluster is rather simple. The only thing you need is to have a createMarkerCluster function.

clusterProps has a field for polygonOptions:

        /*
        * Options to pass when creating the L.Polygon(points, options) to show the bounds of a cluster.
        * Defaults to empty
        */
        polygonOptions?: PolylineOptions | undefined;

Since you now use a plain leaflet plugin it opens up for mor information on the internet, these two might help how you should configure polygonOptions How to make MarkerClusterGroup cluster polygons https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet

Below is my general code to make clustermarkers work with React:

import { createPathComponent } from "@react-leaflet/core";
import L, { LeafletMouseEventHandlerFn } from "leaflet";
import "leaflet.markercluster";
import { ReactElement, useMemo } from "react";
import { Building, BuildingStore, Circle } from "tabler-icons-react";
import { createLeafletIcon } from "./utils";
import styles from "./LeafletMarkerCluster.module.css";
import "leaflet.markercluster/dist/MarkerCluster.css";
type ClusterType = { [key in string]: any };

type ClusterEvents = {
  onClick?: LeafletMouseEventHandlerFn;
  onDblClick?: LeafletMouseEventHandlerFn;
  onMouseDown?: LeafletMouseEventHandlerFn;
  onMouseUp?: LeafletMouseEventHandlerFn;
  onMouseOver?: LeafletMouseEventHandlerFn;
  onMouseOut?: LeafletMouseEventHandlerFn;
  onContextMenu?: LeafletMouseEventHandlerFn;
};

// Leaflet is badly typed, if more props needed add them to the interface.
// Look in this file to see what is available.
// node_modules/@types/leaflet.markercluster/index.d.ts
// MarkerClusterGroupOptions
export interface LeafletMarkerClusterProps {
  spiderfyOnMaxZoom?: boolean;
  children: React.ReactNode;
  size?: number;
  icon?: ReactElement;
}
const createMarkerCluster = (
  {
    children: _c,
    size = 30,
    icon = <Circle size={size} />,
    ...props
  }: LeafletMarkerClusterProps,
  context: any
) => {
  const markerIcons = {
    default: <Circle size={size} />,
    property: <Building size={size} />,
    business: <BuildingStore size={size} />,
  } as { [key in string]: ReactElement };

  const clusterProps: ClusterType = {
    iconCreateFunction: (cluster: any) => {
      const markers = cluster.getAllChildMarkers();

      const types = markers.reduce(
        (
          acc: { [x: string]: number },
          marker: {
            key: string;
            options: { icon: { options: { className: string } } };
          }
        ) => {
          const key = marker?.key || "";
          const type =
            marker.options.icon.options.className || key.split("-")[0];
          const increment = (key.split("-")[1] as unknown as number) || 1;

          if (type in markerIcons) {
            return { ...acc, [type]: (acc[type] || 0) + increment };
          }
          return { ...acc, default: (acc.default || 0) + increment };
        },
        {}
      ) as { [key in string]: number };
      const typeIcons = Object.entries(types).map(([type, count], index) => {
        if (count > 0) {
          const typeIcon = markerIcons[type];
          return (
            <div key={`${type}-${count}`} style={{ display: "flex" }}>
              <span>{typeIcon}</span>
              <span style={{ width: "max-content" }}>{count}</span>
            </div>
          );
        }
      });
      const iconWidth = typeIcons.length * size;

      return createLeafletIcon(
        <div style={{ display: "flex" }} className={"cluster-marker"}>
          {typeIcons}
        </div>,
        iconWidth,
        undefined,
        iconWidth,
        30
      );
    },
    showCoverageOnHover: false,
    animate: true,
    animateAddingMarkers: false,
    removeOutsideVisibleBounds: false,
  };
  const clusterEvents: ClusterType = {};
  // Splitting props and events to different objects
  Object.entries(props).forEach(([propName, prop]) =>
    propName.startsWith("on")
      ? (clusterEvents[propName] = prop)
      : (clusterProps[propName] = prop)
  );

  const instance = new (L as any).MarkerClusterGroup(clusterProps);

  instance.on("spiderfied", (e: any) => {
    e.cluster._icon?.classList.add(styles.spiderfied);
  });
  instance.on("unspiderfied", (e: any) => {
    e.cluster._icon?.classList.remove(styles.spiderfied);
  });

  // This is not used at the moment, but could be used to add events to the cluster.
  // Initializing event listeners
  Object.entries(clusterEvents).forEach(([eventAsProp, callback]) => {
    const clusterEvent = `cluster${eventAsProp.substring(2).toLowerCase()}`;
    instance.on(clusterEvent, callback);
  });
  return {
    instance,
    context: {
      ...context,
      layerContainer: instance,
    },
  };
};
const updateMarkerCluster = (instance: any, props: any, prevProps: any) => {};
const LeafletMarkerCluster = createPathComponent(
  createMarkerCluster,
  updateMarkerCluster
);

const LeafletMarkerClusterWrapper: React.FC<LeafletMarkerClusterProps> = ({
  children,
  ...props
}) => {
  const markerCluster = useMemo(() => {
    return <LeafletMarkerCluster>{children}</LeafletMarkerCluster>;
  }, [children]);
  return <>{markerCluster}</>;
};

export default LeafletMarkerClusterWrapper;

Below is my function to create a marker icon from react elements:

import { divIcon } from "leaflet";
import { ReactElement } from "react";
import { renderToString } from "react-dom/server";

export const createLeafletIcon = (
  icon: ReactElement,
  size: number,
  className?: string,
  width: number = size,
  height: number = size
) => {
  return divIcon({
    html: renderToString(icon),
    iconSize: [width, height],
    iconAnchor: [width / 2, height],
    popupAnchor: [0, -height],
    className: className ? className : "",
  });
};

Not the answer you're looking for? Browse other questions tagged or ask your own question.