mjumbewu/Leaflet.markerdisperse

★ 0Forks 0JavaScriptGitHub ↗Compare

Project website ↗

README

Leaflet.markerdisperse

NPM Version Build Status License: MIT NPM Downloads

View the Interactive Demo!

This is a mapping plugin that will adjust the locations of map markers so that they do not overlap with each other. The behavior should be reminiscent of the Disperse Markers cartographic tool in ArcGIS Pro, or the Point Displacement Renderer in QGIS.

In a sense, this plugin is an antithesis of Leaflet.markercluster, as it will take markers that are within some distance of each other and spread them out, whereas Leaflet.markercluster will take markers that are within some distance of each other and combine them into a single cluster marker.

This plug-in can work with markers in separate layers, as well as with clustering plugins (such as Leaflet.markercluster) such that even marker clusters in different cluster groups can be dispersed when they overlap.

As an example of this plugin achieves (strictly for illustration purposes), a developer might have a set of markers be dispersed like this:

Undispersed Markers Dispersed Markers
Undispersed Markers Dispersed Markers

Or if different types of markers are each clustered in their own layers, the developer might have markers and clusters be dispersed like this:

Undispersed Markers and Clusters Dispersed Markers and Clusters
Undispersed Markers and Clusters Dispersed Markers and Clusters

Installation

Via NPM:

npm install leaflet-markerdisperse

Via CDN:

<script src="https://unpkg.com/[email protected]/dist/leaflet-markerdisperse.umd.js"></script>

Quick Start

import L from 'leaflet';
import 'leaflet-markerdisperse';

const map = L.map('map', {
  markerDisperseOptions: { minimumSpacing: 2 }
}).setView([40.7128, -74.0060], 15);

// Enable dispersal across all layers/clusters on the map
map.markerdisperse.enable();

Dispersal Patterns

This plugin supports different dispersal patterns. Each marker will remember where its original latitude and longitude are. The markers' pixel positions will shift rather than their geographic positions.

Circle (Default)

The circle pattern arranges markers in a circle centered on the geometric mean (centroid) of their original locations, ensuring that none of the markers' bounding boxes overlap. The circle's radius expands just enough to fit all markers without collision.

  • 1 marker: No offset is applied.
  • 2 markers: The markers are pushed apart along the axis connecting their original positions. If they are exactly coincident, they are dispersed vertically.
  • 3 or more markers: The algorithm divides the circle into N equally spaced angular slots. It assigns each marker to the available slot nearest to its original angle from the centroid. This preserves the relative visual direction of each marker as much as possible while maintaining even, compact spacing around the perimeter.

Grid

The grid pattern arranges markers in a compact rectangular grid centered on their geometric mean. The grid dimensions are chosen to minimize the maximum dimension (e.g., 2x2 for 4 markers, 3x2 for 5 markers).

  • Markers are spaced apart by the maximum width and height of the bounding boxes in the group plus the required spacing, guaranteeing no overlap.
  • Each marker is assigned to the closest available grid slot using Manhattan distance, minimizing how far the marker shifts from its original position.

Hexgrid

The hexgrid pattern is similar to the grid pattern, but it staggers every other row horizontally by half the grid spacing to create a hexagonal-like packing layout.

Like the grid pattern, markers are matched to the nearest slot using Manhattan distance to preserve their original locations as much as possible.

General Architecture

This plug-in consists of:

  • a core that, given a set of markers with IDs and display information (pixel positions, anchor points, widths, heights), will calculate offsets of all the markers, and
  • adapters for mapping libraries (starting with Leaflet, perhaps with MapLibre GL and Mapbox GL to follow). The name of the Leaflet adapter is Leaflet.markerdisperse.

This plug-in comes with a full set of unit tests for the core, and for the adapters (e.g. for markers in Leaflet LayerGroups, MarkerClusters, etc.).

Performance Considerations

This plug-in makes every attempt to efficiently calculate a configuration of markers that do not overlap. Though there will be a point at which map performance will suffer as the number of markets increases, we use methods such as spatial indexing and calculating overlaps/shifts of-screen before rendering (because rendering is expensive) to be able to handle hundreds or possibly even thousands of markers. This plugin is inspired by the methods used by Leaflet.markercluster for determining when markers are close enough to each other to affect each other's appearance.

Options

When initializing L.MarkerDisperseGroup or configuring the map handler via markerDisperseOptions, you can provide the following options:

  • minimumSpacing (Number, default: 0): Minimum pixel gap required between marker bounding boxes. 0 means markers will touch but not overlap.
  • pattern (String | Function, default: 'circle'): The dispersal pattern to use. Built-in options are 'circle', 'grid', and 'hexgrid'. You can also provide a custom function.
  • markerSize (Array | L.Point | Function | null, default: null): Explicitly override the dimensions used to calculate spacing. By default, the plugin extracts dimensions directly from each marker's icon. If an override is provided, the plugin will use these dimensions and proportionally scale the marker's original anchor point. If a function is provided, it receives the layer (marker) and should return an [x, y] array or L.Point.

Data Communication/Visualization Considerations

This plugin is a good fit when:

  • You can tolerate markers being a small distance away from their original locations
  • You want to see individual markers even when they are clustered closely together
  • Your markers are all roughtly the same dimensions (width and height)
  • You are willing to accept a small performance penalty for the trade-off of seeing all the markers

Contributors

mjumbewu

Issues