/Leaflet.FreeDraw

Zoopla inspired freehand polygon creation using Leaflet.js and D3.

Primary LanguageJavaScriptMIT LicenseMIT

Leaflet.FreeDraw

Travis   npm   MIT License   IE9+

Use Leaflet.draw for drawing pre-defined polygons and linear shapes – Leaflet.FreeDraw's selling point is that it allows you to freely draw a polygon like Zoopla. Hulls are also supported to normalise polygons when users draw an insane polygon – currently Leaflet.FreeDraw supports Brian Barnett's Graham Scan module and my adaptation of the concave hull algorithm.

L.FreeDraw has been tested in IE9+

Note: For drawing polylines instead, try L.Pather.

FreeDraw Screenshot


Getting Started

L.FreeDraw follows the same convention as other modules, and therefore you should invoke the addLayer on your map instance – passing in an instance of L.FreeDraw.

// Create Leaflet.js instance and then add FreeDraw on top.
var map = L.map('map').setView([51.505, -0.09], 14);
map.addLayer(new L.FreeDraw());

Upon instantiation of L.FreeDraw you can immediately define the mode – with the default being L.FreeDraw.MODES.VIEW – please see modes for more information.

// Allow the user to only create and edit polygons.
map.addLayer(new L.FreeDraw({
    mode: L.FreeDraw.MODES.CREATE | L.FreeDraw.MODES.EDIT
}));

Worth noting is that Leaflet.js often ships with newable equivalents – such as L.map for new L.Mapsee why hereL.FreeDraw follows the same convention and provides a convenient L.freeDraw method for instantiating L.FreeDraw for you whilst passing through the options.

Washes Right Off

Furthermore by invoking the cancelAction method you can cancel the current action – such as drawing a polygon – this method is especially useful for allowing the user to cancel their action by pressing the escape key.

Fetching Markers

Once the user has created, deleted, or edited a polygon, you'll likely wish to load in markers based on the polygons visible – with L.FreeDraw the event markers is emitted with an array of L.LatLng objects in the first argument as eventData.latLngs:

freeDraw.on('markers', function getMarkers(eventData) {
    var latLngs = eventData.latLngs;
    // ...
});

Once you have your markers it's entirely up to you to add them to your Leaflet.js map using the marker methods.

Options

FreeDraw has quite a few options – all of which can be seen by taking a look at the L.FreeDraw.Options object. However, there are certain options that you are likely to use more than others.

// Prevent the rendering of the polygon via a convex/concave hull.
freeDraw.options.setHullAlgorithm(false);

// Utilise Brian Barnett's convex hull.
freeDraw.options.setHullAlgorithm('brian3kb/graham_scan_js');

// ...Or my adaptation of the concave hull.
freeDraw.options.setHullAlgorithm('Wildhoney/ConcaveHull');

For the hull algorithm implementations, take a look at the following paper on convex hulls and concave hulls.

All of the polygons drawn with L.FreeDraw can be modified using the options and standard CSS.

Once the user has drawn their free-hand drawing, it is converted into a polygon by Leaflet.js – you can define how smooth the rendered polygon is by using the setSmoothFactor method – by default the smoothFactor is 5.

Polygon Mutation

When a user is modifying a polygon the markers event is emitted each and every time – which may be overkill, especially if your requests are somewhat time consuming. In this case L.FreeDraw allows you to defer the fetching of markers for when the edit mode has been exited with freeDraw.options.setBoundariesAfterEdit(true).

Polygon Intersection

By invoking the freeDraw.allowPolygonMerging(true) method, L.FreeDraw will attempt to join up any polygons that intersect.

Exit Create Mode

After drawing a polygon the L.FreeDraw.MODES.CREATE mode will automatically be exited – but this can be suppressed by specifying freeDraw.options.exitModeAfterCreate(false) in which case the create mode will be persisted until the user explicitly exits it.

Modes

FreeDraw by default uses the L.FreeDraw.MODES.VIEW mode which prevents the user from creating, editing, or deleting any polygons. When instantiating L.FreeDraw you may override the default mode – in the following case a user may only delete polygons:

var freeDraw = new L.FreeDraw({
    mode: L.FreeDraw.MODES.DELETE
});

In specifying the mode you are using bitwise operators with the mapping being as follows:

L.FreeDraw.MODES = {
    VIEW:        1,
    CREATE:      2,
    EDIT:        4,
    DELETE:      8,
    APPEND:      16,
    EDIT_APPEND: 4 | 16,
    ALL:         1 | 2 | 4 | 8 | 16
}

Therefore you're able to combine the bitwise operators to specify multiple modes. For example, if you would like to allow the user to create and delete, then you would specify the options as L.FreeDraw.MODES.CREATE | L.FreeDraw.MODES.DELETE. By allowing a user to perform every action you would have to concatenate all of the modes via the pipe (|) character – therefore L.FreeDraw provides the convenient L.FreeDraw.MODES.ALL property which does that for you.

Using the L.FreeDraw.MODES.ALL property you could easily enable all the modes except edit with the following: L.FreeDraw.MODES.ALL ^ L.FreeDraw.MODES.EDIT.

All modes allow the user to zoom and drag except when you have the L.FreeDraw.MODES.CREATE enabled – even when used in conjunction with other modes.

It's quite likely that you'll want to change the mode as the user interacts with your application – for this you have the setMode method which accepts an aforementioned bitwise operator for determining what actions the user is able to perform.

// Change the mode to allow the user to only edit and delete polygons.
var freeDraw = new L.FreeDraw();
freeDraw.setMode(L.FreeDraw.MODES.EDIT | L.FreeDraw.MODES.DELETE);

L.FreeDraw also ships with the freeDraw.unsetMode for unsetting a mode based on the current mode.

You may also listen to updates of the mode using the freeDraw.on('mode') event.

Elbow Creation

Using the L.FreeDraw.MODES.APPEND mode you can allow users to create new edges on the polygon. If both the L.FreeDraw.MODES.APPEND and L.FreeDraw.MODES.DELETE modes are active at the same time then some logic is applied to decide whether the user wishes to delete or create a new edge. However if L.FreeDraw.MODES.APPEND is active and L.FreeDraw.MODES.DELETE is not then any click on the polygon will create a new edge – and vice-versa for the inverse.

If you would like to control the edge size in which a new polygon will be created when both aforementioned modes are active, you can do so using the setMaximumDistanceForElbow method where the default is currently set to 10.

Even when the L.FreeDraw.MODES.APPEND mode is active exclusively, you still may not wish for any click to add a new elbow, and therefore by enabling the addElbowOnlyWithinDistance mode a click on the polygon will stay pay attention to the setMaximumDistanceForElbow value.

Class Names

Depending on the mode you can apply different CSS styles – for example when the user is not in edit mode you probably wish to hide the edges – by default all edges would be hidden, and only enabled when the mode-edit class has been applied to the map node:

section.map.mode-edit div.polygon-elbow {
    opacity: 1;
    pointer-events: all;
}

Each mode maps to a different class which is conditionally applied to the map based on whether that mode is active:

  • mode-view maps to L.FreeDraw.MODES.VIEW;
  • mode-create maps to L.FreeDraw.MODES.CREATE;
  • mode-edit maps to L.FreeDraw.MODES.EDIT;
  • mode-delete maps to L.FreeDraw.MODES.DELETE;
  • mode-append maps to L.FreeDraw.MODES.APPEND;

Another example would be changing the cursor type when the user is in polygon creation mode:

section.map.mode-create {
    cursor: crosshair;
}

You may change the class name of the polygon edges with the setIconClassName method, and the SVG class name with setSVGClassName.

Common Issues

Invisible Drawing Path

When you're drawing a polygon on the map, the path that is being drawn is invisible – this is caused by a handful of missing styles that you need to apply to the svg.tracer node:

svg.tracer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

It's worth noting that the above style properties may well be changed to suit your circumstances, but this is a good starting point.