[GIS] Cluster GeoJSON (linestrings and markers) into circles using Leaflet

clusteringgeojsonleafletlinestringmarkers

I have different hiking routes (in geoJSON format) displayed on a map. They contain linestrings, multilinestrings and markers. Currently, it looks like this: http://www.hikethisway.today/Sweden.html.

I would like to cluster the routes in circles where the size of the circle is determined by the length of the route (have a distance attribute in the geoJSON's). Something like this: https://atendesigngroup.com/blog/openlayers-plus
So when zooming out to a certain level, the circle clusters appear and when clicking on a circle cluster the route (line + markers) appear.

I'm currently using Leaflet and the desired output in the second link is using Openlayers to create the visualization. Is it possible to achieve the same result using Leaflet or will I have to move to Openlayers?

Best Answer

Yes, it is definitely possible.

There would be nothing too complex about it, but it depends exactly on how is structured your GeoJSON data for each hiking route (sounds like your route includes a LineString and some Points).

The idea would be to gather your hiking route (i.e. LineString and Points) into a Leaflet Layer Group, and to associate it with its Circle Marker representation. You can adjust the circle radius according to some property of your LineString. The position of the circle could be computed as the center of the route bounds for example, but you could use any other computation you wish.

Then, using the map "zoomend" event, you determine whether you want to show the Circle Marker or the Layer Group.

var groups = [];

// Process each single routes (only one here)
// and add them into the groups global array.
// This assumes each "single route" contains only 1 LineString.
groups.push(processSingleRoute(singleRoute));

map.on('zoomend', onZoomEnd);
onZoomEnd();

function onZoomEnd() {
  var newZoom = map.getZoom();

  if (newZoom >= 12) {
    groups.forEach(function (group) {
      map.removeLayer(group.circle);
      map.addLayer(group.route);
    });
  } else {
    groups.forEach(function (group) {
      map.addLayer(group.circle);
      map.removeLayer(group.route);
    });
  }
}

function processSingleRoute(singleRouteGeoJSON) {
  var singleRouteContainer = {};

  singleRouteContainer.route = L.geoJSON(singleRouteGeoJSON, {
    onEachFeature: function (feature, layer) {
      if (feature.geometry.type === 'LineString') {
        // Compute a "center" of the route.
        var center = layer.getBounds().getCenter();

        // Assuming only 1 LineString in singleRouteGeoJSON.
        singleRouteContainer.circle = L.circleMarker(center, {
          radius: feature.properties.distance // in pixels
        }).bindPopup('Distance: ' + feature.properties.distance);
      }
      layer.bindPopup('Name: ' + feature.properties.name + '<br />Distance: ' + feature.properties.distance);
    }
  });

  return singleRouteContainer;
}

Live demo: https://plnkr.co/edit/3E4i8FjgDPrB8bc1uV9z?p=preview