📜 ⬆️ ⬇️

Clustering markers on the Google Maps API map

Hi, Habr! I want to talk about my experience in developing a map 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 the UX and display the data visually more clearly than a bunch of overlapping points. The company in which I work creates a unique product for the media, this is a mobile application, the meaning of which is to take photos / videos / streams of materials and the opportunity to receive excellent compensation from the media if the editor uses your material in the publication. I am developing a SPA application on the react / redux stack for moderating the content sent by users. Recently, I faced the task of making an interactive map on which one could see the location of users and send them a push notification if an interesting event occurs nearby.

Here is what I had to do:


')
The first thing that came to my mind was to look for a ready 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. I chose google-map-react because it allowed 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 a svg element as a marker, there are solutions in the network that describe the tricky html box in as a marker, but google-map-react represents this out of the box.

Going further, on the layout you can see 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 are writing so that we think that this is a kind of lodash for the reactor. But for those who are not familiar with recompose, it will be immediately clear to everyone, so I adapted this example and removed the extra dependency.

To begin with, we will set properties for the google-map-react and state components, we will render a map with pre-prepared markers:
(api key we get 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 <GoogleMapReact defaultZoom={MAP.defaultZoom} defaultCenter={MAP.defaultCenter} options={MAP.options} onChange={this.handleMapChange} yesIWantToUseGoogleMapApiInternals bootstrapURLKeys={{ key: 'yourkey' }} > {this.state.clusters.map(item => { if (item.numPoints === 1) { return ( <Marker key={item.id} lat={item.points[0].lat} lng={item.points[0].lng} /> ); } return ( <ClusterMarker key={item.id} lat={item.lat} lng={item.lng} points={item.points} /> ); })} </GoogleMapReact> 

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

For example, 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), })); 

Each time the map scale / center changes, 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 to the supercluster, and we get clusters at the output. Thus, the supercluster simply combined the coordinates of the points lying nearby and produced a new point with its coordinates and an array of points where all the entered points lie.

Demo can be seen here.
Sample source code here

Source: https://habr.com/ru/post/334644/


All Articles