Showing posts with label Leaflet. Show all posts
Showing posts with label Leaflet. Show all posts

Friday, October 10, 2014

Book Review: Leaflet.js Essentials by Paul Crickard III


This is a review of Leaflet.js Essentials by Paul Crickard III.

Contents

  • Chapter 1 introduces creating a basic map from scratch with leaflet, how to use map tiles, how to create popups, and how to enable geolocation. 
  • Chapter 2 shows how to create GeoJSON data, how to style GeoJSON layers by type (LineString, Polygon, etc.), and how to filter to display only a subset of the data. 
  • Chapter 3 introduces the leaflet.heat plugin for density heat maps and the heatmap.js library for intensity heat maps. It also teaches how to style and animate heat maps, how to create choropleths, and how to use buttons outside the map to change what's shown in the map. 
  • Chapter 4 introduces how to create custom markers and marker shadows as well as several markers that are available online. We also learn how to cluster markers, animate markers, and create pie or bar chart markers. 
  • Chapter 5 shows how to use many ESRI resources in Leaflet, including map tiles, shapefiles, and geocoding. 
  • Chapter 6 introduces using Node.JS, Python, and C# with leaflet, including getting data via AJAX calls, using MongoDB to save created map points, and creating desktop applications with C#.

Review

This is a very well laid-out, detailed guide to Leaflet. It is intended for complete newbies to Leaflet, but it provides some very advanced information as well. The book claims you should have some JavaScript knowledge before beginning, but I'm not entirely sure this is necessary, since Leaflet maps rely on only very minimal JavaScript.

I enjoyed the breadth of this book. I think the book does a great job of covering the information a beginner would need. It also provides information on a wide variety of topics like good tips for designing for mobile, a very comprehensive list of map tiles (many of these I did not know about), and information on several JavaScript libraries and Leaflet plug-ins that allow for additional functionality within the maps.

On the other hand, there is one major criticism I have with this book, especially for a beginner audience. I'm not that happy with the included code samples or how they are organized. This may just be personal preference, but I prefer books that have the "before this section" code and the "after this section" code so you can start with the before and then double-check with the after if you made an error or didn't understand something. I feel like the provided code samples made it difficult to follow along with programming while reading.

All in all, I wouldn't hesitate to recommend this book to someone beginning to learn Leaflet. For more advanced Leaflet users, there are some gems that may or may not make the book a worthwhile purchase. I will say that I use Leaflet almost every day, and I did learn several things from reading Leaflet.js Essentials.

4 out of 5 stars


Note: The publisher asked me to review this book and provided my copy.

Thursday, March 27, 2014

Add Icons to Layer Control in Leaflet

When you have multiple layers, it can help to add a legend to the map. This tutorial shows how to add icons to the layer control in Leaflet. This tutorial shows only marker layers, but you could create icons for polygon layers and use those as well.

Legend with marker icons
The following code sets up our map, layers, and the layer control. This is the standard way maps look in Leaflet, with no visual indication of which layer goes with which markers, without checking the boxes on and off.


var map = L.map('map', {
  center: [33.8, -84.4],
  zoom: 11,
    layers: [trainLayer, treeLayer]
});

var overlayMaps = {
  "Train": trainLayer,
  "Tree": treeLayer
};

L.control.layers(null, overlayMaps, {
  collapsed: false
}).addTo(map);

Default legend
In order to add a legend to the layer control, you can simply change the overlayMaps code to add any HTML you'd like. In this case, we'll add the map icon pngs with a small height so they will look okay in the layer control:

var overlayMaps = {
  "<img src='http://mollietaylor.com/skills/js/leaflet/train.png' height=24>Train": trainLayer,
  "<img src='http://mollietaylor.com/skills/js/leaflet/arbol.png' height=24>Tree": treeLayer
};

It's really that simple! I searched the documentation and the web, and I couldn't figure out how to do this, so I figured I'd just try adding the HTML. Turns out it looks great!

Full code available on CodePen. Map icons by Nicolas Mollet and Axel Rodriguez.

Thursday, March 13, 2014

Changing Leaflet Slidemapper to a Side-by-Side View

The default setup in slidemapper is to have the slides either above or below the map. However, it is also possible to place the slides to one side of the map.

Slides beside map

CSS changes

We need to make a couple of changes to how the smapp-show and smapp-map classes are displayed. You'll want to play around with these some, but this is what worked well for me:


    .smapp-show { 
      width:50%; 
    }
    .smapp-show .slide { 
      overflow-y:visible; 
    }
    .smapp-map { 
      margin:10px; 
      width:48%; 
      height:95%; 
      position:absolute; 
      top:0; 
      right:0; 
    }

Slidemapper changes

On the slidemapper side of things, the only edit we need to make is changing the options of our slideshow. Specifically, we need to edit the slideHeight and mapHeight to something appropriate for having the slides beside the map. For example, I chose:

    $mySlideMap = $('#slideshow-container').slideMapper({
      slideHeight: 540,
      mapHeight: '98%'
    });

And that's all the changes you need to make to have a side-by-side slideshow in slidemapper! The full code is available as a gist.

References


Thursday, March 6, 2014

Link to Another Slide in Leaflet Slidemapper

The slidemapper plugin for Leaflet is very useful, but when building large slideshows, it can be annoying that there is no easy way to link to specific slides. For example, you might want to create a table of contents or link from the last slide back to the first. Fortunately, there's a way to move to specific slides, instead of just advancing or moving back by 1. We'll use jQuery to do this. We already have jQuery loaded for slidemapper.

Specify Where Links Will Go

After the $mySlideMap code in your html file, you can create a list of links that you will later add to the slides. Generally, this will be the last thing in the <script> tag at the end of the body.

For example, in the sample code from the slidemapper example, the <script> initially contains:

    $mySlideMap = $('#slideshow-container')
    .slideMapper();

    $mySlideMap.slideMapper('add', EXAMPLEDATA);

At the end of the script, let's add a link to the first slide and a link to the last slide. The jQuery we use allows any code with a specific class to be transformed into a link. We can combine that with the slidemapper move method to create links to specific slides.

    $('.toc').click(function() {
      $mySlideMap.slideMapper('move', 0, true);
    });

    $('.end').click(function() {
      $mySlideMap.slideMapper('move', EXAMPLEDATA.length - 1, true);
    });

Add the Links

Next, we need to actually include the links in the slides. The links references will go in the data file. In the example, the file is named data.js.

Here's a link to the last slide from the first slide:

  // intro marker
  {
    icon: 'other.png',
    marker: [42.516846, -70.898499],
    center: [40.423, -98.7372],
    html: '<table style="margin:0 40px; padding:10px"><tr>' +
            '<td><img src="http://placehold.it/300x180&text=Map+Stuff"/></td>' +
            '<td style="padding-left:10px">' +
              '<h1>SlideMapper FTW!</h1>' +
              '<p>This is a demo of the different sorts of slides you can setup using slidemapper.</p>' +
              '<p><a class="end">Skip to the end.</a></p>' +
            '</td>' +
          '</tr></table>',
    popup: 'So it begins!'
  },

And here's a link from the last slide to the first slide:

  // empty slide
  {
    html: '<div style="margin:0 40px; padding:20px 10px">' +
            '<div>' +
              '<h2>The End</h2>' +
              '<p>Goodbye.</p>' +
              '<p><a class="toc">Return to Table of Contents.</a></p>' +
            '</div>' +
          '</div>'
  }


Prettifying

Because of the unusual way these links are made using jQuery, we should probably add some css to make them look like links. I added the following css to the <head>, but you can add any css to the head or as a separate stylesheet.

  <style type="text/css">
    a {
      color: orange;
      cursor: pointer;
      text-decoration: underline;
    }
  </style>



Here's an example of a link from one slide to another
And now you have an easy way to navigate through a long slidemapper deck. You could even create a table of contents at the beginning and then link back to that from each slide.

The full code is available in a gist.

References

Thursday, February 27, 2014

Center Map on Layer Change in Leaflet

In Leaflet, it can be helpful to change the bounds of the map when the user adds or changes the visible map layers.

The Basics

First, we'll start with the initial code including our map and polygon layers:

  var circle = L.circle([51.508, -0.11], 500, {
      color: 'red',
      fillColor: '#f03',
      fillOpacity: 0.5
  });

  var polygon = L.polygon([
      [51.509, -0.08],
      [51.503, -0.06],
      [51.51, -0.047]
  ]);

  var map = L.map('map', {
    center: [51.505, -0.09],
    zoom: 13
  });

  var overlayMaps = {
    "Circle": circle,
    "Polygon": polygon
  };

  L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
  }).addTo(map);

  L.control.layers(overlayMaps, null, {
    collapsed: false
  }).addTo(map);

So far, most of this should be familiar from the Leaflet Quick Start Guide.

Next, we want to add a listener function that will zoom and re-center upon a change in the circle or polygon layer.

The .on method of map allows you to watch for an event to occur and then execute a function when it does. In this case, we want to wait for the event 'baselayerchange'. This way the map will automatically zoom and recenter when the user changes layers.


  map.on('baselayerchange', function(e) {
    console.log(e);
    map.fitBounds(e.layer);
  });


The map automatically zooms to the bounds of the shape when the layer is activated.

Options

There are a few options for exactly how the map is zoomed and/or re-centered and for what type of layers are affected.

If you want to do this with overlay layers instead of base layers, you can substitute 'overlayadd' for 'baselayerchange'. Using overlay layers is more common for drawing shapes, but treating your layers as base layers makes it easy to display only one at a time.

  map.on('overlayadd', function(e) {
    console.log(e);
    map.fitBounds(e.layer);
  });

fitBounds automatically zooms to the tightest zoom level where the whole shape is visible. If you don't want to use fitBounds (say you're centering on a new overlay layer and don't want to zoom all the way in), you can use setView or panTo instead. panTo animates as the view changes.

  map.on('overlayadd', function(e) {
    console.log(e);
    map.panTo(e.layer);
  });

And there you have a few different ways to center and/or zoom in when an overlay layer is added or when the baselayer is changed. I'd recommend you check out the documentation for fitBounds, setView, and panTo and play around with the options. The options for L.control.layers are also helpful. For example, you can set collapsed to false to encourage users to change the layers.

The full code is available in a gist.

References


Sunday, February 2, 2014

LWIMW3: Trail Magic

I just finished my submission for Look What I Made Weekend 3. Look What I Made Weekend (LWIMW) is a chance for people to create something over the course of 48 hours. The concept is based on Ludum Dare and other game jams, but for LWIMW you don't have to make a game. Instead, you are free to pursue any creative endeavor and show off your results at the end.

NB: The content below is mostly a reprint of my submission at LWIMW.

My project is an interactive website that will be part of the companion site to a book my friend Scott Thigpen is writing.
I didn't quite start from scratch on this project. This image shows the progress I had made before the weekend. You can also view it on the web.

pre-LWIMW

The other images show the current status after the weekend.
I made a lot of progress this weekend. I added GPS routes to the map, added a table of contents, added marker clustering, improved the graphic design (CSS and basemap), and added about 25% of the final content.
post-LWIMW

post-LWIMW

The only essential things I have left to do are adding the rest of the content, working with my friend on palette and graphics, and tweaking some small things.

You can view the current state of the Trail Magic site here.

Thursday, January 30, 2014

Append Layer to overlayMaps in Leaflet

What if we want to create a layer based on geolocation, but have the layer only be added to the map once geolocation occurs? I didn't find this example in any Leaflet tutorials, but it's pretty simple with some basic JavaScript

I'll start with some code that should look familiar from the Leaflet Quick Start Guide, but with each item as its own layer.

  var marker = L.marker([51.5, -0.09]);

  var circle = L.circle([51.508, -0.11], 500, {
      color: 'red',
      fillColor: '#f03',
      fillOpacity: 0.5
  });

  var polygon = L.polygon([
      [51.509, -0.08],
      [51.503, -0.06],
      [51.51, -0.047]
  ]);

  var map = L.map('map', {
    center: [51.505, -0.09],
    zoom: 13,
    layers: [marker, circle, polygon]
  });

  var overlayMaps = {
    "Marker": marker,
    "Circle": circle,
    "Polygon": polygon
  };
  L.tileLayer('http://tile.cloudmade.com/[API-KEY]/29889/256/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
    maxZoom: 18
  }).addTo(map);

Next let's add the code that will display the three original layers if geolocation does not occur. In this case, we do not want the geolocated layer to show.


  function onLocationError(e) {
    alert(e.message);

    L.control.layers(null, overlayMaps).addTo(map);
  }

  map.on('locationerror', onLocationError);

You can see in the below screenshot that only the three layers are displayed.

With Geolocation Off
Finally, we need to add code that will display all the layers (including the geolocation layer) if geolocation does occur. We simply need to add the "yourLocation" layer to overlayMaps and then display the layers as normal.


  function onLocationFound(e) {
    yourLocation = L.marker(e.latlng);

    overlayMaps["You"] = yourLocation;

    L.control.layers(null, overlayMaps).addTo(map);
  }

  map.on('locationfound', onLocationFound);

The screenshots below show what happens once geolocation has occurred.

With Geolocation On

Showing Geolocation Layer
 The full code is available in a gist.

References

Thursday, January 23, 2014

Add and Remove Leaflet Circle on Click

Similar to the popup example in the Leaflet tutorial, you might want to allow a user to add a circle centered on the point they click.

After defining your map, you first need to declare the variable you'll be using:

  var clickCircle;

While popups automatically disappear on the next click, circles do not. The following code removes the former circle from the leaflet map before drawing the new circle:

  function onMapClick(e) {
    if (clickCircle != undefined) {
      map.removeLayer(clickCircle);
    };

Next, we want our function to draw the new circle (NB: "1609 * 3" is the radius of the circle. This makes the radius equal 3 miles):

    clickCircle = L.circle(e.latlng, 1609 * 3, {
      color: '#f07300',
      fillOpacity: 0,
      opacity: 0.5
    }).addTo(map);
  }


And finally, we need to add an event listener so that the function will be run when a user clicks on the map:


  map.on('click', onMapClick);


The full code is available in a gist.

References



Thursday, January 16, 2014

Embed Audio in Leaflet Pop-up

Here's how to embed audio in a leaflet map. This example will also show you how to embed most other HTML in a leaflet map, or how to embed audio in an HTML file.

In the html file:

  function onEachAudio(feature, layer) {
    layer.bindPopup(feature.properties.name + "<br>" + feature.properties.html);
  };

  new L.GeoJSON.AJAX("audio.geojson", {
    onEachFeature: onEachAudio,
    pointToLayer: function(feature, latlng) {
      return L.marker(latlng, {icon: audioIcon});
    }
  }).addTo(map);

And here's the geojson format where you'll include your audio HTML and the coordinates where you want to view each file:


{
  "type": "FeatureCollection",
  "features": [
  {
    "type": "Feature",
    "properties": {
      "name": "<a href='http://www.freesound.org/people/genghis%20attenborough/sounds/212798/'>Deep basement</a>",
      "html": "<p><audio width='300' height='32' src='http://www.freesound.org/data/previews/212/212798_205108-lq.mp3' controls='controls'><br />Your browser does not support the audio element.<br /></audio></p>"
    },
    "geometry": {
      "type": "Point",
      "coordinates": [-100,34]
    }
  },{
    "type": "Feature",
    "properties": {
      "name": "<a href='http://www.freesound.org/people/John%20Sipos/sounds/125696/'>Atlantis docks then lands.</a>",
      "html": "<p><audio width='300' height='32' src='http://www.freesound.org/data/previews/125/125696_593024-lq.mp3' controls='controls'><br />Your browser does not support the audio element.<br /></audio></p>"
    },
    "geometry": {
      "type": "Point",
      "coordinates": [-84,40]
    }
  }
  ]
}

Just substitute in the address of your audio files for the sample files above in the "html" property of the geojson features.
Example Leaflet Map with Audio


The full code is available in a gist.

Thursday, January 9, 2014

Import JSON Data from an External File in Leaflet

If your JSON or GeoJSON data is long, you might want to store it in a separate file to make your code more readable or to reduce repetition and allow you to access the same data file from multiple pages. You can do that with jQuery.

First, load jQuery 1.10.2 or the newest version of jQuery (2.0 onward does not support Internet Explorer 6, 7, or 8) in the header:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

Next, in your leaflet script at the bottom of the body, you'll need to add all your other code and your basemap, like normal, for example:

  var map = L.map('map').setView([38.0740, -55], 3);

  function onEachFeature(feature, layer) {
    layer.bindPopup(feature.properties.City + ", " + feature.properties.State + ", " + feature.properties.Country);
  }

Then, you can just use jQuery to get the JSON or GeoJSON file. A simple version of this would be:

  $.getJSON("cities.geoJSON", function (cities) {
    L.geoJson(cities).addTo(map);
  })

But you can also add more options to your L.geoJson code, just like you would if using data stored in the HTML file. For example:


  $.getJSON("cities.geoJSON", function (cities) {
    L.geoJson(cities, {
      onEachFeature: onEachFeature,
      pointToLayer: function (feature, latlng) {
        switch (feature.properties.Remember) {
          case '1': return L.marker(latlng, {icon: visitedIcon});
          case '?': return L.marker(latlng, {icon: uncertainIcon});
          case '0': return L.marker(latlng, {icon: uncertainIcon});
        }
      }
    }).addTo(map);
  })

So just a couple lines of jQuery allow you to store your JSON or GeoJSON data in a separate file.

The example code is available in a gist and the example is viewable here.

Further Reading

Thursday, December 19, 2013

Make All Polygons the Same Shade in Leaflet

The Quick Start tutorial shows us how to change the color of a polygon:

var circle = L.circle([51.508, -0.11], 500, {
    color: 'red',
    fillColor: '#f03',
    fillOpacity: 0.5
}).addTo(map);

But what if we want to change the color and style of all (or a set of the) polygons?

First we can define the style:

var defaultStyle = {
  color: 'green',
  fillOpacity: 0.2
};

And then we can just add that style to our polygons:

var polygon = L.polygon([
    [51.509, -0.08],
    [51.503, -0.06],
    [51.51, -0.047]
]).setStyle(defaultStyle).addTo(map);

var circle = L.circle([51.508, -0.11], 500).setStyle(defaultStyle).addTo(map);


The full code (based on the Quick Start tutorial) is available in a gist.