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 )
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:
With each change in the scale / center of the map, we will recalculate the clusters:
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.
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.