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


Thursday, February 20, 2014

Merge by City and State in R

Often, you'll need to merge two data frames based on multiple variables. For this example, we'll use the common case of needing to merge by city and state.

First, you need to read in both your data sets:

# import city coordinate data:
coords <- read.csv("cities-coords.csv",
  header = TRUE,
  sep = ",")

# import population data:
data <- read.csv("cities-data.csv",
  header = TRUE,
  sep = ",")

Next comes the merge. You can use by.x and by.y to declare which variables the merge will be based on. If the variables have exactly the same name in both data sets, you can use by instead of by.x and by.y.

x and y represent the two data sets you are merging, in that order.

You also want to state whether you want to include all data from either data set, using all or all.x and all.y. In this case, we want to make sure we hold onto all our city data, even data for the cities we do not have coordinates for.

# merge data & coords by city & state:
dataCoords <- merge(coords, data, 
  by.x = c("City", "State"),
  by.y = c("city", "state"),
  all.x = FALSE,
  all.y = TRUE)

Running that code shows what we would expect. Houston is included in the final data set even though there are no coordinates for it, while Dallas is not included since it has coordinates but no data:

            City State Latitude  Longitude year population
1        Chicago    IL 41.85003  -87.65005 2012    2714856
2       Columbus    GA 32.46098  -84.98771 2012     198413
3       Columbus    OH 39.96118  -82.99879 2012     809798
4       Columbus    OH 39.96118  -82.99879 2010     787033
5    Los Angeles    CA 34.05223 -118.24368 2012    3857799
6       New York    NY 40.71427  -74.00597 2012    8336697
7       New York    NY 40.71427  -74.00597 2010    8175133
8  San Francisco    CA 37.77823 -122.44250 2012     825863
9  San Francisco    CA 37.77823 -122.44250 2010     805235
10       Houston    TX       NA         NA 2012    2160821


Bonus

If you'd like to get a list of which cases got merged in but lack coordinate data, there's a simple line of code to do that:

> dataCoords[!complete.cases(dataCoords[,c(3,4)]),]
      City State Latitude Longitude year population
10 Houston    TX       NA        NA 2012    2160821

Also, you might want to tidy up the names of your variables, if they followed different conventions in their respective initial data sets:


> names(dataCoords) <- c("City", "State", "Latitude", "Longitude", "Year", "Population")
> dataCoords
            City State Latitude  Longitude Year Population
1        Chicago    IL 41.85003  -87.65005 2012    2714856
2       Columbus    GA 32.46098  -84.98771 2012     198413
3       Columbus    OH 39.96118  -82.99879 2012     809798
4       Columbus    OH 39.96118  -82.99879 2010     787033
5    Los Angeles    CA 34.05223 -118.24368 2012    3857799
6       New York    NY 40.71427  -74.00597 2012    8336697
7       New York    NY 40.71427  -74.00597 2010    8175133
8  San Francisco    CA 37.77823 -122.44250 2012     825863
9  San Francisco    CA 37.77823 -122.44250 2010     805235
10       Houston    TX       NA         NA 2012    2160821


The full sample code is available as a gist.

References


gist

Change R code tabs to 2 spaces.

bold package names
italicize functions

http://hilite.me/
manni

for R posts, use: sessionInfo()

Thursday, February 13, 2014

ggplot Fit Line and Lattice Fit Line in R

Let's add a fit line to a scatterplot!

Fit Line in Base Graphics

Here's how to do it in base graphics:

ols <- lm(Temp ~ Solar.R,
  data = airquality)

summary(ols)

plot(Temp ~ Solar.R,
  data = airquality)
abline(ols)

Fit line in base graphics in R



Fit Line in ggplot

And here's how to do it in ggplot:

library(ggplot2)
ggplot(data = airquality,
    aes(Solar.R, Temp)) + 
  geom_point(pch = 19) + 
  geom_abline(intercept = ols$coefficients[1],
    slope = ols$coefficients[2])

You can access the info from your regression results through ols$coefficients.

Edit: Thanks to an anonymous commenter, I have learned that you can simplify this by using geom_smooth.  This way you don't have to specify the intercept and slope of the fit line.


ggplot(data = airquality,
    aes(Solar.R, Temp)) + 
  geom_point(pch = 19) + 
  geom_smooth(method = lm,
    se = FALSE)

Fit line in ggplot in R

Fit Line in Lattice

In lattice, it's even easier. You don't even need to run a regression; you can just add to the type option.


library(lattice)

xyplot(Temp ~ Solar.R,
  data = airquality,
  type = c("p", "r"))

Fit Line in Lattice in R
The code is available in a gist.

References


Thursday, February 6, 2014

Compare Regression Results to a Specific Factor Level in R

Including a series of dummy variables in a regression in R is very simple. For example,

ols <- lm(weight ~ Time + Diet,
  data = ChickWeight)
summary(ols)

The above regression automatically includes a dummy variable for all but the first level of the factor of the Diet variable.

Call:
lm(formula = weight ~ Time + Diet, data = ChickWeight)

Residuals:
     Min       1Q   Median       3Q      Max 
-136.851  -17.151   -2.595   15.033  141.816 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  10.9244     3.3607   3.251  0.00122 ** 
Time          8.7505     0.2218  39.451  < 2e-16 ***
Diet2        16.1661     4.0858   3.957 8.56e-05 ***
Diet3        36.4994     4.0858   8.933  < 2e-16 ***
Diet4        30.2335     4.1075   7.361 6.39e-13 ***
---
Signif. codes:  0***0.001**0.01*0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 35.99 on 573 degrees of freedom
Multiple R-squared:  0.7453,  Adjusted R-squared:  0.7435 
F-statistic: 419.2 on 4 and 573 DF,  p-value: < 2.2e-16

This is great, and it's often what you want. But in this case, it's comparing each of the diets to Diet1. In some cases, you might want to compare to a specific diet that isn't the first in the factor list.

How can we choose which dummy to compare to? Fortunately, it's simple to compare to a specific dummy in R. We can just relevel the factor so the dummy we want to compare to is first.

ChickWeight$Diet <- relevel(ChickWeight$Diet,
  ref = 4)

olsRelevel <- lm(weight ~ Time + Diet,
  data = ChickWeight)
summary(olsRelevel)

The "ref" argument allows us to change the reference level of the factor variable. This means that when we perform regression analysis

You can use table or str to find the factor levels, if you don't already know them.

After releveling the factor variable, we can simply perform the same regression again, and this time it will compare the results to the new reference level:

olsRelevel <- lm(weight ~ Time + Diet,
  data = ChickWeight)
summary(olsRelevel)


Call:
lm(formula = weight ~ Time + Diet, data = ChickWeight)

Residuals:
     Min       1Q   Median       3Q      Max 
-136.851  -17.151   -2.595   15.033  141.816 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  41.1578     4.0828  10.081  < 2e-16 ***
Time          8.7505     0.2218  39.451  < 2e-16 ***
Diet1       -30.2335     4.1075  -7.361 6.39e-13 ***
Diet2       -14.0674     4.6665  -3.015  0.00269 ** 
Diet3         6.2660     4.6665   1.343  0.17989    
---
Signif. codes:  0***0.001**0.01*0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 35.99 on 573 degrees of freedom
Multiple R-squared:  0.7453,  Adjusted R-squared:  0.7435 
F-statistic: 419.2 on 4 and 573 DF,  p-value: < 2.2e-16

Now we can choose any factor level as the reference for the series of dummies in the regression analysis. The code is available in a gist.

Reference