Thursday, 30 January 2014

Using the Leaflet.draw plugin for leaflet.js


The following post is a portion of the Leaflet Tips and Tricks book which is free to download. To use this post in context, consider it with the others in this blog or just download the the book as a pdf / epub or mobi .
----------------------------------------------------------

Leaflet.draw

Leaflet.draw adds support for drawing and editing vectors and markers overlaid onto Leaflet maps. Its driving force is Jacob Toye (a good Hamilton lad, so he gets a special shout-out :-)).
It is a comprehensive plugin that can add polylines, polygons, rectangles, circles and markers to a map and then edit or delete those objects as desired. It has an extensive range of options for configuring the drawing objects ‘look and feel’. It’s code is supported on GitHub and it can be downloaded from there. There is also some great documentation on function and use for the plugin that should be the authority for use.
Leaflet.draw is less of an endpoint, than an enabler of additional functionality. I say this because while it gives us the ability to draw to our hearts content on a map, it also provides the framework to take those drawn objects and push them to a database or similar (which we won’t cover in this overview sorry).
What we will go over though is how to add Leaflet.draw to our simple base map and how to configure some of the wide range of options.
Here’s what we are aiming to show in terms of the range of controls and options on the left hand side of our map.
Leaflet.draw toolbar
And here’s some example objects produced with the plugin.
Leaflet.draw sample objects
The colours are configurable as we shall see in the coming pages.

Leaflet.draw code description

The following code listing is the bare minimum that should be considered for use with Leaflet.draw. I even hesitate to say that, because the following is really only suitable for demonstrating that you have it running correctly. The configuration options that we will work through in the coming pages will add considerable functionality and will be captured in a separate example that will be available in the Appendices and online on GitHub.
<!DOCTYPE html>
<html>
<head>
    <title>Simple Leaflet Map</title>
    <meta charset="utf-8" />
    <link 
        rel="stylesheet" 
        href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
    />
    <link 
        rel="stylesheet" 
        href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css"
    />
</head>
<body>
    <div id="map" style="width: 600px; height: 400px"></div>

    <script
        src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js">
    </script>
    <script
        src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js">
    </script>

    <script>
        var map = L.map('map').setView([-41.2858, 174.78682], 14);
        mapLink = 
            '<a href="http://openstreetmap.org">OpenStreetMap</a>';
        L.tileLayer(
            'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; ' + mapLink + ' Contributors',
            maxZoom: 18,
            }).addTo(map);

        var drawnItems = new L.FeatureGroup();
        map.addLayer(drawnItems);

        var drawControl = new L.Control.Draw({
            edit: {
                featureGroup: drawnItems
            }
        });
        map.addControl(drawControl);

        map.on('draw:created', function (e) {
            var type = e.layerType,
                layer = e.layer;
            drawnItems.addLayer(layer);
        });

    </script>
</body>
</html>
There are only three ‘blocks’ that have changed in the code from our simple map example.
The first is an additional link to load more CSS code;
    <link 
        rel="stylesheet" 
        href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css"
    />
(As with the leaflet.css file which is loaded before hand, I have taken some small formatting liberties to make the code appear more readable on the page.)
This loads the file directly from the Leaflet.draw repository on GitHub, so if you are loading from a local file you will need to adjust the path appropriately.
The second is the block that loads the leaflet.draw.js script.
    <script
        src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js">
    </script>
Leaflet.draw exists as a separate block of JavaScript code and again, here we are loading the file directly from the Leaflet.draw repository on GitHub (as per the earlier advice, if you are loading from a local file you will need to adjust the path appropriately).
The last change to the file is the block of code that runs and configures Leaflet.draw.
        var drawnItems = new L.FeatureGroup();
        map.addLayer(drawnItems);

        var drawControl = new L.Control.Draw({
            edit: {
                featureGroup: drawnItems
            }
        });
        map.addControl(drawControl);

        map.on('draw:created', function (e) {
            var type = e.layerType,
                layer = e.layer;
            drawnItems.addLayer(layer);
        });
The var drawnItems = new L.FeatureGroup(); line adds a new extended layer group to the map called drawnItems. This is the layer that the elements we create will be stored on.
Then the map.addLayer(drawnItems); line adds the layer with our drawn items to the map.
Next we get to the first of the true Leaflet.draw commands when we initialize the draw control and pass it the feature group of editable layers;
        var drawControl = new L.Control.Draw({
            edit: {
                featureGroup: drawnItems
            }
        });
        map.addControl(drawControl);
This is required when adding the edit toolbar and tells the Leaflet.draw plugin which layer (drawnItems) should be editable. Then the controls are added to the map (map.addControl(drawControl);).
Finally when we add a new vector or marker we need prompt a trigger that captures the type of item we have created (polyline, rectangle etc) and adds it to the drawn items layer on the map.
        map.on('draw:created', function (e) {
            var type = e.layerType,
                layer = e.layer;
            drawnItems.addLayer(layer);
        });
This is alto the part of the code where you could store the information that described the element in a database or similar.

Leaflet.draw configuration options

As I mentioned earlier, the sample code described above is extremely cut down and should be extended using the wide range of options available to Leaflet.draw.
OBJECT COLOURS
As our first change, if we use the simple example, all of the elements we generate have the same colour, so lets change that first.
Leaflet.draw map with common colours
Changing the options is a simple matter of declaring them when we initialize the draw controls by adding them as required by the documentation on the Leaflet.draw GitHub page. For example in the following code snippet we have added in the draw: option which in turn has options for each of the shapes. We have entered thepolygon: option which has it’s own options of which we have added shapeOptions: as an option. And as if that wasn’t enough we select the option for color: from this and finally declare it as purple.
        var drawControl = new L.Control.Draw({
   draw: {
    polygon: {
     shapeOptions: {
      color: 'purple'
     },
    },
   },
            edit: {
                featureGroup: drawnItems
            }
        });
        map.addControl(drawControl);
This might seem slightly confusing, but it’s just a simple hierarchy which we can flesh out by doing the same thing for each of the remaining shapes (polyline, rectangle and circle). The code snippet would then look as follows;
  var drawControl = new L.Control.Draw({
   draw: {
    polygon: {
     shapeOptions: {
      color: 'purple'
     },
    },
    polyline: {
     shapeOptions: {
      color: 'red'
     },
    },
    rect: {
     shapeOptions: {
      color: 'green'
     },
    },
    circle: {
     shapeOptions: {
      color: 'steelblue'
     },
    },
   },
   edit: {
    featureGroup: drawnItems
   }
  });
  map.addControl(drawControl);
And our new colours look like this…
Leaflet.draw map with various colours
POLYGON LINE INTERSECTION
When drawing a polygon it is very easy to cross the lines when describing our object on the screen, and while this may be a desired action, in general it is probably not. However as our code stands, if we tell the script to cross the lines and draw a polygon it will do it with a (perhaps unintended) result looking something like the following…
Leaflet.draw polygon with crossed lines
Luckily there is an option that will provide a warning that this is happening while drawing and will allow you to correct and carry on. The following screen shot shows the results when trying to place a point that allows boundary lines to cross;
Leaflet.draw polygon with crossed lines
We can see that not only is a warning raised, but the shape colour changes.
This is accomplished by alteration of the polygon options as follows;
    polygon: {
     shapeOptions: {
      color: 'purple'
     },
     allowIntersection: false,
     drawError: {
      color: 'orange',
      timeout: 1000
     },
    },
This has introduced the allowIntersection option and set it to false and provided the drawError option with the instructions to change the colour of the object to orange for 1000 milliseconds.
This option will also work with polyline objects.
SHOW AND MEASURE AN AREA
While tracing a polygon we can get Leaflet.draw to report the total area spanned by the shape by setting theshowArea option to true.
Leaflet.draw polygon showing area
You can see from the screen shot that the area is in hectares, but we can set the measurement units to not be metric (to show acres instead) by setting the metric option to false. The code for the polygon now looks like this;
    polygon: {
     shapeOptions: {
      color: 'purple'
     },
     allowIntersection: false,
     drawError: {
      color: 'orange',
      timeout: 1000
     },
     showArea: true,
     metric: false
    },
REPEATING A DRAWING OPTION AUTOMATICALLY
By default once we have finished drawing a polygon, if we wanted to draw another, we would need to click on the polygon tool on the toolbar to start again. But we can use the repeatMode set to true to continue to dray polygons until we select another object to draw or until we press the escape key.
Our polygon option code will now look like this;
    polygon: {
     shapeOptions: {
      color: 'purple'
     },
     allowIntersection: false,
     drawError: {
      color: 'orange',
      timeout: 1000
     },
     showArea: true,
     metric: false,
     repeatMode: true
    },
This option will work with all the other drawing objects.
PLACE AN ALTERNATIVE MARKER
If an alternative marker has been declared (see section on setting up different markers) it can be specified under the marker option as an alternative icon.
The code to set up an alternative icon duplicates is covered elsewhere, but consists of the following;
  var LeafIcon = L.Icon.extend({
   options: {
    shadowUrl: 
        'http://leafletjs.com/docs/images/leaf-shadow.png',
    iconSize:     [38, 95],
    shadowSize:   [50, 64],
    iconAnchor:   [22, 94],
    shadowAnchor: [4, 62],
    popupAnchor:  [-3, -76]
   }
  });

  var greenIcon = new LeafIcon({
   iconUrl: 'http://leafletjs.com/docs/images/leaf-green.png'
   });
Here we are using one of the markers (the green leaf) set up as part of the custom icons tutorial on GitHub.
And the option code to be added to include an alternative marker is;
    marker: {
     icon: greenIcon
    },
And here’s a pretty picture of the green marker.
Add a custom marker
PLACE THE LEAFLET.DRAW TOOLBAR IN ANOTHER POSITION
The toolbar position can also be changed. This is configured via an option that is quite high up the hierarchy (in parallel with the draw and edit options). So this should be added directly under the drawControl declaration per the following code snippet;
  var drawControl = new L.Control.Draw({
   position: 'topright',
This will place the toolbar in the top right hand corner of the map as follows;
Moving the Leaflet.draw toolbar
The other options for positioning are bottomrightbottomleft and the default of topleft.
The full code and a live example of the use of the Leaflet.draw plugin with the options described here in the appendices and is available online at bl.ocks.org or GitHub. A copy of all the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.

The description above (and heaps of other stuff) is in the Leaflet Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).

9 comments:

  1. " Leaflet.draw is less of an endpoint, than an enabler of additional functionality. I say this because while it gives us the ability to draw to our hearts content on a map, it also provides the framework to take those drawn objects and push them to a database or similar (which we won’t cover in this overview sorry)."

    I would truly appreciate a link where i can find more information on how to push objects to database or json. thank you

    ReplyDelete
    Replies
    1. Hi Damian, That sort of function is a little more complex than I have explained in any of my books so far. However, the way I learned how to do it was simply an extension of the chapter on using MySQL in D3 Tips and Tricks (https://leanpub.com/D3-Tips-and-Tricks). The only problem is that it will require a bit of a learning curve on your part that I don't have examples for sorry. If it's any consolation that is an idea that would make a great new section for the book. Thanks for the question and good luck.

      Delete
  2. Thanks - I found this page very helpful for getting the leaflet.draw controls set up. Sometimes the basics are deemed so "simple" that they get glossed over elsewhere!

    ReplyDelete
  3. How to get the lat n long value of selected polygon

    ReplyDelete
    Replies
    1. sorry, I'm not sure. It's not something that I've tried before.

      Delete
  4. Hi. Great tutorial, I am really excited to try this out. But I am really noob with Leaflet and when I use your sample code, my map doesn't have any drawing options? Just a zoom in and out.

    ReplyDelete
    Replies
    1. Cheers. I think that we're probably the victim of the various parts of the code examples being really old (over four years). If I go to a live example that I did a few years ago I can see the same result http://bl.ocks.org/d3noob/7730264. So there's good news and bad news. The good news is that if you're using the code examples correctly, it's not you, it's the code. The bad news is that it doesn't work :-(. The good news is that Leaflet continues to be totally awesome and if you go to this page there are a range of the latest leaflet plugins including some new draw ones http://leafletjs.com/plugins.html#edit-geometries. I can't make the promise that they will just work automagically, but have a play and don't be afraid of trying lots of different things before it works. Start simple till you get it going and then build from there. Good luck and thanks for the comment.

      Delete
    2. Thanks for the advice! I have come pretty far since my original post and it's been an awesome experience. I can even export the drawn items as geoJSONs now which is exciting :) I really want these to be converted to .kml though but I am having no luck with the "tokml" plugin.

      Delete
  5. Great tutorial, you have saved me a lot of time.

    ReplyDelete