Clustering Markers on the Google Maps API Map

Hello, Habr! I want to talk about my experience in developing maps with clustered markers on google maps api and React.js. Clustering is a grouping of nearby markers, tags, points in one cluster. This helps to improve UX and display data visually more clearly than a bunch of dots piled on top of each other. The company I work for creates a unique product for the media, this is a mobile application, the meaning of which is to shoot photo / video / stream materials and the opportunity to get excellent compensation from the media if the editors use your material in the publication. I am developing a SPA application on the react / redux stack to moderate user submitted content. Recently, I faced the task of creating an interactive map on which it would be possible to see the location of users and send them a push notification,

Here's what I had to do:



The first thing that came to my mind was to look for a ready-made solution for react.js. I found 2 top google-map-react and react-google-maps libraries . They are wrappers over the standard Google maps API, presented as components for react.js. My choice fell on google-map-react because it allowed you to use any JSX element as a marker, let me remind you that the standard google maps api tools allow you to use an image and svg element as a marker, there are solutions on the network that describe the tricky insertion of html structures in as a marker, but google-map-react represents this out of the box.

We go further, the layout shows that if the markers are close to each other, they are combined into a group marker - this is clustering. In the readme google-map-react, I found an example of clustering, but it was implemented using recompose - this is a utility that creates a wrapper over function components and higher-order components. The creators write that we think that this is a kind of lodash for the reaction. But those who are unfamiliar with recompose vryatli will immediately understand everything, so I adapted this example and removed the unnecessary dependency.

First, set the properties for google-map-react and state components, render a map with pre-prepared markers:
(we get the api key here )

const MAP = {
  defaultZoom: 8,
  defaultCenter: { lat: 60.814305, lng: 47.051773 },
  options: {
    maxZoom: 19,
  },
};
state = {
  mapOptions: {
    center: MAP.defaultCenter,
    zoom: MAP.defaultZoom,
  },
  clusters: [],
};
//JSX 

  {this.state.clusters.map(item => {
    if (item.numPoints === 1) {
      return (
        
      );
    }
    return (
      
    );
  })}

There will be no markers on the map, as the this.state.clusters array is empty. To combine markers into a group, we use the supercluster library .

For an example, we generate points with coordinates:

const TOTAL_COUNT = 200;
export const susolvkaCoords = { lat: 60.814305, lng: 47.051773 };
export const markersData = [...Array(TOTAL_COUNT)]
  .fill(0) // fill(0) for loose mode
  .map((__, index) => ({
    id: index,
    lat:
      susolvkaCoords.lat +
      0.01 *
        index *
        Math.sin(30 * Math.PI * index / 180) *
        Math.cos(50 * Math.PI * index / 180) +
      Math.sin(5 * index / 180),
    lng:
      susolvkaCoords.lng +
      0.01 *
        index *
        Math.cos(70 + 23 * Math.PI * index / 180) *
        Math.cos(50 * Math.PI * index / 180) +
      Math.sin(5 * index / 180),
  }));

With each change in the scale / center of the map, we will recalculate the clusters:

handleMapChange = ({ center, zoom, bounds }) => {
  this.setState(
    {
      mapOptions: {
        center,
        zoom,
        bounds,
      },
    },
    () => {
      this.createClusters(this.props);
    }
  );
};
createClusters = props => {
  this.setState({
    clusters: this.state.mapOptions.bounds
      ? this.getClusters(props).map(({ wx, wy, numPoints, points }) => ({
          lat: wy,
          lng: wx,
          numPoints,
          id: `${numPoints}_${points[0].id}`,
          points,
        }))
      : [],
  });
};
getClusters = () => {
  const clusters = supercluster(markersData, {
    minZoom: 0,
    maxZoom: 16,
    radius: 60,
  });
  return clusters(this.state.mapOptions);
};

In the getClusters method, we feed the generated points in a supercluster, and the output is clusters. Thus, the supercluster simply combined the coordinates of the points lying nearby and produced a new point with its coordinates and the points array, where all the entered points lie.

You can see the demo here.
Source code of the example here.

Also popular now: