Skip to content

A Leaflet plugin to add/move/remove points on routes, lines and polygons by drag&drop.

License

Notifications You must be signed in to change notification settings

FacilMap/Leaflet.DraggableLines

Repository files navigation

Leaflet.DraggableLines

Leaflet.DraggableLines is a Leaflet plugin that makes it possible to change the shape of routes, lines and polygons by drag&drop. It add the following interactions to Polylines and its subclasses (such as Polygons):

  • A marker is added to each point on the line. This point can be dragged around to change the shape of the line. Unless disabled via the removeOnClick option, clicking/tapping this point will remove it (unless that would cause the line to have less than 2 (3 for polygons) points).
  • While hovering anywhere on the line, a temporary marker appears that follows the mouse cursor. This point can be clicked or dragged to add an additional corner/waypoint in that place. On touch devices without a mouse cursor (smartphones, tablets), tapping a line will add a new point at the tapped position. This point can then be dragged around.
  • Unless disabled via the allowExtendingLine option, a plus icon is rendered at the beginning and end of each line (not for polygons). Clicking/tapping will add a new point before the first point or after the last point of the line. Hovering this icon will show a temporary marker that can be dragged around to add a new point there.

There is support for MultiPolylines, for Polygons with holes and for Polylines based on calculated routes (where dragging should update the route points rather than the coordinates of the Polyline).

Tip: Use it together with Leaflet.HighlightableLayers to make it easier to drag thin lines.

Demo

Usage

Since release 2.0.0, Leaflet.DraggableLines is published as an ES module only. If you are using a module bundler, you can install it using npm install -S leaflet-highlightable-layers and import it in your code:

import DraggableLines from 'leaflet-draggable-lines';

...

TypeScript is supported.

If you want to use Leaflet.DraggableLines in a static HTML page without using a module bundler (not recommended in production), you need to make sure to import it and Leaflet as a module, for example from esm.sh:

<script type="importmap">
	{
		"imports": {
			"leaflet": "https://esm.sh/leaflet",
			"leaflet-draggable-lines": "https://esm.sh/leaflet-draggable-lines"
		}
	}
</script>
<script type="module">
	import L from "leaflet";
	import DraggableLines from "leaflet-draggable-lines";

	...
</script>

Simple usage for lines and polygons

const map = L.map('map');

new L.Polyline([53.09897, 12.02728], [52.01701, 14.18884], { color: '#0000ff', weight: 10 }).addTo(map);

const draggable = new DraggableLines(map);
draggable.enable();

By default, DraggableLines makes all Polylines and Polygons with interactive: true on the map draggable, including ones that are added after it has been enabled.

Usage for routes

A route is usually a Polyline whose points are calculated to be the best path to get from one place to another, potentially via some specified waypoints. When you drag a route, you don't want the shape of the underlying Polyline to be modified directly, but you rather want a new waypoint to be inserted and the route to be recalculated based on that.

Leaflet.DraggableLines adds an additional option draggableLinesRoutePoints to Polyline and all classes inheriting from it. Passsing this option will cause Leaflet.DraggableLines to modify the points in this option rather than the points of the line itself when the user interacts with the line. (Note: To update the route points of an existing line, call layer.setDraggableLinesRoutePoints(latlngs) rather than setting the option by hand. This way Leaflet.DraggableLines will notice the change and update the drag markers accordingly.)

Since the value of this option does not have any effect on the line itself, you need to recalculate the route every time the user has changed the course of the route. You can do this by subscribing to the events dragend (the user has finished dragging a point), remove (the user has removed a point by clicking on it) and insert (the user has added a point by clicking the line).

const route = new L.Polyline([], { draggableLinesRoutePoints: [[53.09897, 12.02728], [52.01701, 14.18884]] }).addTo(map);
async function updateRoute() {
	route.setLatLngs(await calculateRoute(route.getDraggableLinesRoutePoints()));
}
updateRoute();

const draggable = new DraggableLines(map);
draggable.enable();

draggable.on("dragend remove insert", (e) => {
	if (e.layer === route)
		updateRoute();
});

It is also possible to continuously update the route as the user is dragging. For this, it is advisable to wrap the updateRoute function with debounce to make sure that the route is not calculated on every pixel.

draggable.on("drag", debounce((e) => {
	if (e.layer === route)
		updateRoute();
}));

Enabling for specific layers only

You can use the enableForLayer option to decide which layers are made draggable automatically. By default, this callback returns true for all Polylines that have interactive: true (including polygons because L.Polygon is a sub-type of L.Polyline). The callback is only called for Polylines and its sub-types. Other types are not supported.

An example how to control whether dragging is enabled for a particular layer would be to introduce a custom option:

L.Polyline([[53.09897, 12.02728], [52.01701, 14.18884]], { enableDraggableLines: true }).addTo(map);
L.Polyline([[52.93871, 11.92566], [52.73629, 12.57935]], { enableDraggableLines: false }).addTo(map);

new DraggableLines(map, {
	enableForLayer: (layer) => layer.options.enableDraggableLines
}).enable();

An alternative way is to manually enable dragging for specific layers using the enableForLayer method. To disable automatic enabling for all layers, pass a function that always returns false as enableForLayer. As a short-hand, enableForLayer also supports passing a boolean directly:

const layer1 = L.Polyline([[53.09897, 12.02728], [52.01701, 14.18884]]).addTo(map);
const layer2 = L.Polyline([[52.93871, 11.92566], [52.73629, 12.57935]]).addTo(map);

const draggable = new DraggableLines(map, {
	enableForLayer: false
});
draggable.enable();
draggable.enableForLayer(layer1);

Options

You can pass the following options as the second parameter to DraggableLines:

  • enableForLayer: Configures for which layers dragging should be enabled automatically. When DraggableLines is enabled by calling enable(), all Polylines on the map are checked against this. If a Polyline is added to the map later, is is also checked against this value.

    Supports different types:

    • A callback that receives a Polyline layer as its parameter and returns a boolean
    • An array of Polylines
    • A single Polyline
    • A boolean to enable/disable it for all layers.

    By default this is a callback that returns true for all Polylines that have interactive: true.

  • dragMarkerOptions: A callback that should return the marker options for the draggable markers that are added at every line point. Receives 3 arguments: the Polyline layer, the index of this marker in the list of line points (starting with 0) and the number of line points. By default, returns a green marker for the first line point, a red marker for the last one and a blue marker for all other line points. By default, the marker is on the marker pane behind all other markers.

  • tempMarkerOptions: A callback that should return the marker options for the temporary marker that appears when hovering a line. Receives the Polyline layer as an argument. By default, returns a blue marker.

  • plusMarkerOptions: A callback that should return the marker options for the plus marker that appears at the start and end of a line (unless allowExtendingLine is false). Receives 2 arguments: the Polyline layer and a boolean that is true if the marker is at the start of the line or false if it is at the end. By default, renders a plus in a circle on the overlay pane behind all other overlays.

  • plusTempMarkerOptions: A callback that should return the marker options for the temporary marker that appears when hovering the plus plus markers. Receives 2 arguments: the Polyline layer and a boolean that is true if the marker is at the start of the line or false if it is at the end. By default, renders a green marker at the start of the line and a red marker at the end, on the marker pane behind all other markers.

  • allowDraggingLine: If true (default), users are allowed to insert new line points by hovering and then dragging the line or by clicking it. Instead of a boolean, you can also specify a layer object, an array of layer objects or a (layer) => boolean callback.

  • allowExtendingLine: If true (default), users are allowed to drag the line in a way that adds additional points before the first point and after the last point. This will add draggable plus icons before the start and after the end of each line. Has no effect on Polygons. Instead of a boolean, you can also specify a layer object, an array of layer objects or a (layer) => boolean callback.

  • removeOnClick: If true (default), points are removed when clicking them. If a polyline has only 2 points or a polygon only 3, clicking will have no effect. Instead of a boolean, you can also specify a layer object, an array of layer objects or a (layer, idx) => boolean callback.

Note: If you want to enable dragging behaviour without showing any drag markers, you need to pass an invisible icon with the dimensions of the desired draggable areas.

Events

These events are fired on the DraggableLines instance.

dragstart, drag, dragend

dragstart is fired when the user starts to drag a marker. drag is fired continuously as the user moves the marker around (if you subscribe to this event, it might make sense to debounce the handler). dragend is fired after the user releases the mouse.

All 3 events carry an object with the following properties:

  • layer: The Polyline which is being dragged

  • from: An L.LatLng instance with the coordinates where the dragging started. In case of a point marker, this will be the coordinates of the point. In case of a temporary marker, these coordinates will be exactly on the line, but they will most likely not equal any existing point, but rather be somewhere in between two existing points.

  • to: An L.LatLng instance with the coordinates where the marker is currently being dragged. In case of a dragstart event, this is equal to from. In case of a dragend event, this is the final position of the drag marker.

  • idx: The index where the new point was inserted. For example, on a Polyline that has 3 points A, B and C, and the user starts dragging the line between point A and B, idx will be 1, because the new point is inserted at index 1 (latlngs.splice(event.idx, 0, event.to)).

    In case of a multi-line (a Polyline constructed with an array or arrays of coordinates) or a Polygon (where getLatLngs() always returns an array of arrays of coordinates, regardless of whether it was constructed as such), idx will be a tuple (array) of two numbers rather than a single number. The tuple describes the position in the array or arrays, with the first number describing the index in the top array and the second number describing the index in the nested array.

    See below for some helper methods that make it easier to insert a point into an array of coordinates or an array of arrays of coordinates.

  • isNew: true if the dragged marker is a temporary marker (adding a new point to the line). false if it is an existing point.

remove

Fired when the user removed a point by clicking it. Only fired if the removeOnClick options is not disabled.

Carries an object with the following properties:

  • layer: The Polyline from which the point has been removed
  • idx: The index where the point was removed. A number for simple Polylines, a tuple of two numbers for a MultiPolyline or a Polygon.

insert

Fired when the user added a point by clicking the line.

Carries an object with the following properties:

  • layer: The Polyline which has been clicked
  • idx: The index where the point was inserted. A number for simple Polylines, a tuple of two numbers for a MultiPolyline or a Polygon.
  • latlng: An instance of L.LatLng with the coordinates of the new point.

dragmouseover, dragmouseout

Fired when the user hovers or unhovers one of the draggable markers that is rendered on every line point.

Carries an object with the following properties:

  • layer: The Polyline which the drag marker belongs to
  • marker: The marker, an instance of DraggableLines.DragMarker
  • idx: The index to which point the marker belongs. A number for simple Polylines, a tuple of two numbers for a MultiPolyline or a Polygon.

tempmouseover, tempmousemove, tempmouseout

Fired as the user hovers or unhovers the line and a temporary drag marker is shown/hidden.

Carries an object with the following properties:

  • layer: The Polyline which is hovered
  • marker: The temporary marker, an instance of DraggableLines.TempMarker
  • idx: The index where a route point would be inserted if the user started dragging from here. A number for simple Polylines, a tuple of two numbers for a MultiPolyline or a Polygon. Note that this can change as the user hovers across the line. The tempmouseout event may have a different idx than its preceding tempmouseover event.
  • latlng: The position where the temporary marker is currently rendered. This is an exact position on the line.

plusmouseover, plusmouseout

Fired as the user hovers or unhovers the plus icons at the beginning and end of a line.

Carries an object with the following properties:

  • layer: The Polyline to which the plus icons belong
  • marker: The temporary marker which is rendered on top of the plus icon, an instance of DraggableLines.PlusTempMarker (which is a sub-class of DraggableLines.TempMarker)
  • plusMarker: The marker that contains the plus icon, an instance of DraggableLines.PlusMarker.
  • idx: The index where a route point would be inserted if the user started dragging from here. A number for simple Polylines, a tuple of two numbers for a MultiPolyline. Because plus icons are only rendered at the very beginning and end of each line, the index is either 0 or equal to the length of route points.

Methods

These methods can be called on instances of DraggableLines.

  • enableForLayer(layer): Manually enable dragging for a specific layer.
  • disableForLayer(layer): Manually disable dragging for a specific layer.
  • redraw(): Reapply the options and redraw the drag markers for all lines. Call this after making any changes to the options property to apply the changes.
  • redrawForLayer(layer): Redraw the drag markers for a specific line.

Polyline extensions

Leaflet.DraggableLines adds various methods and options to the Polyline subclass. These can be used with any instances of Polyline and its subclasses.

Options

  • draggableLinesRoutePoints: An array of LatLng expressions that represent the waypoints of the route that this Polyline represents. When this option is set, Leaflet.DraggableLines renders the points from this array instead of the ones from getLatLngs() as draggable markers, and when the route is dragged it modifies this array instead of the actual points of the line.

    This is only supported for single polylines, it is not supported for Polygons or MultiPolylines where getLatLngs() returns an array of arrays.

    To update the route points of an existing line, use the setDraggableLinesRoutePoints method rather than setting layer.options.draggableLinesRoutePoints manually, to make sure that DraggableLines notices the change and updates the positions of the drag markers.

Methods

  • hasDraggableLinesRoutePoints(): Returns a boolean whether this Polyline has draggableLinesRoutePoints set.
  • getDraggableLinesRoutePoints(): Returns the draggableLinesRoutePoints option as an array of L.LatLng instances.
  • setDraggableLinesRoutePoints(routePoints): Update the draggableLinesRoutePoints option.

Events

  • draggableLines-setLatLngs: Fired after the points of this layer are updated using the setLatLngs() method.
  • draggableLines-setRoutePoints: Fired after the draggableLinesRoutePoints option is updated using the setDraggableLinesRoutePoints method.

Usage with rectangles

L.Rectangle is a sub-class of L.Polygon, so it is made draggable by default. Since rectangles are a special type of polygon, their behaviour differs in the following ways:

  • Rectangles always have 4 drag markers corresponding to their corners. Their indexes are 0 (south-west), 1 (north-west), 2 (north-east) and 3 (south-east).
  • Dragging any of the drag markers will keep the shape rectangular, so it will affect the positions of one or two other drag markers.
  • It is not possible to insert additional waypoints by clicking the border of the rectangle. No temporary drag marker is shown on hover, even when allowDraggingLine is set to true.
  • It is not possible to remove waypoints from rectangles. Nothing will happen when clicking the drag markers, even when removeOnClick is set to true.
  • The draggableLinesRoutePoints option is ignored for rectangles.