Mapbox Clusters with Ruby on Rails
Aggregate points on a map in clusters with Ruby on Rails and Mapbox
When we have a geocoded model in Rails, we may want to display the records in our database as markers on a map. However, once you begin to have too many records close to each other, it ruins the map experience for your users.
One solution to this problem is to aggregate markers that are close to each other in clusters which break apart as soon as the zooms in. Mapbox’s mapbox-gl
API has this functionality and we’ll implement it within the context of Rails.
We’re going to be using Ruby on Rails 5 with Webpacker 4 and Turbolinks, but the setup should be similar with other versions. In our example, we have a Car
model that is geocoded — it has latitude
and longitude
coordinates.
Let’s set up the backend first.
We want to display a map with Car
instances on the index
action of CarsController
:
class CarsController < ApplicationController
# ...
def index
@cars = Car.where.not(latitude: nil, longitude: nil) @geojson = build_geojson
end private def build_geojson
{
type: "FeatureCollection",
features: @cars.map(&:to_feature)
}
end
end
We only want to display Car
instances that have been properly geocoded, so we exclude those without latitude
and longitude
. Then we build the geojson
object required by Mapbox to build our clusters. We’re delegating the responsibility of each instance to render itself as a feature:
class Car < ApplicationRecord
# ... def coordinates
[longitude, latitude]
end def to_feature
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": coordinates
},
"properties": {
"car_id": id,
"name": name,
"info_window": ApplicationController.new.render_to_string(
partial: "cars/infowindow",
locals: { car: self }
)
}
}
end
Later we’ll want to open a popup when we click on an individual point on the map, so we’re rendering it as HTML and adding it to our feature hash.
Finally, we pass the geojson
to the view so it can be picked up by JavaScript:
<div id="map"
data-cars="<%= @geojson.to_json %>"
data-mapbox-api-key="<%= ENV['MAPBOX_API_KEY'] %>">
</div>
First, we add Mapbox GL JS API to our Rails app with yarn add mapbox-gl
.
There are several parts in displaying the cluster. Once the map loads, we need to add a geojson
data source, which we parse from the data attributes we added to the map element:
map.on('load', function() {
const cars = JSON.parse(mapElement.dataset.cars);
map.addSource('cars', {
type: 'geojson',
data: cars,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
}
Then we need to add three layers to the maps. One to display the cluster circles which aggregate our geojson
features. Another one to display the count of aggregated features inside each cluster circle. Finally, a layer to display the circle for unaggregated or individual features.
Next, we want to set up some events. For the aggregated clusters, we want the cursor to become a pointer once we hover on a cluster and we want to zoom in on and center the map on that cluster on click.
For unaggregated features, we want to center the map on click and also display a pointer cursor when hovering on them.
Here’s the full code:
You can check a live example app here: https://mapbox-clusters.herokuapp.com/ and the source code here: https://github.com/rodloboz/mapbox-clusters