11

New here and relatively new to R also, so please forgive apriori and let me know what I'm doing wrong in this post to avoid annoying others in the future:

I am trying to create a sequence (Sep-1971 to Apr-1972) of leaflet maps. In the end, I'd like to crunch them into shiny and have a user play/pause an animation (shiny looping animation slider).

No while and for loops worked for me. Increments had worked when I checked my is after running the code, the leaflet functions not. Without the loop, my "Dynamic Leaflet Fails" (see below in code section) worked and opened a map.

Is it not possible to create leaflets sequentially?

#set working directory
require(leaflet)
require(dplyr)

#Build data.frame with 10 obs + 3 cols
power <- data.frame(Latitude <-c(33.515556, 38.060556, 47.903056, 49.71, 49.041667, 31.934167, 54.140586, 54.140586, 48.494444, 48.494444), Longitude <- c(
129.837222, -77.789444, 7.563056, 8.415278, 9.175, -82.343889, 13.664422, 13.664422, 17.681944, 17.681944), start <- c(as.Date(
"15-Sep-1971", "1-Dec-1971", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Apr-1972", "1-Apr-1972", "24-Apr-1972", "24-Apr-1972", format = "%d-%b-%Y")))

#"Dynamic" leaflet Fails1: While+For combo
i<- as.Date("1971-09-14")
while (i < as.Date("1972-05-01")) {    for(star in start){
if (star > i) {
leaflet(power) %>% addTiles() %>%
  addCircleMarkers(lng = ~Longitude, lat = ~Latitude)
}}
i <- i+60}

#"Dynamic" leaflet Fails2: For+break combo
lap <- seq(as.Date("1971-09-14"), as.Date("1972-05-01"), by = "month")
for(i in lap) {
leaflet (data = power[power$start > i,]) %>%
addTiles() %>%
addCircleMarkers(lng = ~Longitude, lat = ~Latitude)  
if (i > as.Date("1951-01-01")) 
{      break }}
Naibaf
  • 417
  • 5
  • 15
  • Given that leaflet builds dynamic maps, it's a better idea to build one map where you can adjust the start date. `shiny` makes it pretty easy. Otherwise, if you're using leaflet to make static maps (not a great idea) build a plotting function, or at least store the maps you're building so you can do something with them. – alistaire Apr 11 '16 at 17:16
  • Thanks alistaire! What's the difference between a (single) dynamic and static leaflet map? If there is one, I am missing something huge. As I see it, leaflet won't let shiny interfere with adjustments, but I may be wrong and I am open to suggestions. With all of this, I am trying to create sth like this: (https://seth127.shinyapps.io/slider/) but I could not find code from any other previous project where shiny's looping animation and leaflet were combined. If you have a suggestion how to go about this, please enlighten me! – Naibaf Apr 11 '16 at 20:50
  • Uh! -3 on research effort. Back to undergrad and my heart's all broken. In all seriousness, I gave this a lot of trial and erroring and surfing the web, perhaps more than what my post appears to have involved. Share your brains with me, plz – Naibaf Apr 11 '16 at 20:52
  • Here's a single leaflet map of all my coordinates I want to plot: (http://homepage.univie.ac.at/a1009991/power.html) Plotting the markers sequentially really tells an amazing story, guys, it would be awesome to get it up and running! Any ideas? I'd like to create sth like this: (http://skeate.github.io/Leaflet.timeline/earthquakes.html) – Naibaf Apr 11 '16 at 20:57
  • _Dynamic_ in the sense that it builds HTML maps, not JPG or PNG ones. If you want to animate, an easy option is `ggplot2`+`ggmaps`+`gganimate`. To be really lazy, chuck the whole thing on CartoDB; to be less so, use [its R package](https://github.com/CartoDB/cartodb-r). You can do it with Leaflet with its `add*` and `remove*` functions, but you'll have to build in timing, which may take some work. – alistaire Apr 12 '16 at 02:32
  • Thanks, I knew `ggplot2`, but I hadn't heard of `gganimate`. I'll give this a try, many thanks, alistaire – Naibaf Apr 12 '16 at 06:42
  • 1
    I disagree with the -3, and I don't know of anyone else that has demonstrated a solution to this, so I'll attempt an answer shortly. – timelyportfolio Apr 12 '16 at 21:42

2 Answers2

16

Here is a quick way to add leaflet-timeline in the way you suggest. For some reason, the timeline does not render perfectly in RStudio Viewer, but it does seem to work correctly in Chrome. I commented inline in the code to describe the steps.

library(htmlwidgets)
library(htmltools)
library(leaflet)
library(geojsonio)

#Build data.frame with 10 obs + 3 cols
power <- data.frame(
  "Latitude" = c(33.515556, 38.060556, 47.903056, 49.71, 49.041667, 31.934167, 54.140586, 54.140586, 48.494444, 48.494444),
  "Longitude" = c(129.837222, -77.789444, 7.563056, 8.415278, 9.175, -82.343889, 13.664422, 13.664422, 17.681944, 17.681944),
  "start" = do.call(
    "as.Date",
    list(
      x = c("15-Sep-1971", "1-Dec-1971", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Apr-1972", "1-Apr-1972", "24-Apr-1972", "24-Apr-1972"),
      format = "%d-%b-%Y"
    )
  )
)

# set start same as end
#  adjust however you would like
power$end <- power$start


# use geojsonio to convert our data.frame
#  to GeoJSON which timeline expects
power_geo <- geojson_json(power,lat="Latitude",lon="Longitude")

# create a leaflet map on which we will build
leaf <- leaflet() %>%
  addTiles()

# add leaflet-timeline as a dependency
#  to get the js and css
leaf$dependencies[[length(leaf$dependencies)+1]] <- htmlDependency(
  name = "leaflet-timeline",
  version = "1.0.0",
  src = c("href" = "http://skeate.github.io/Leaflet.timeline/"),
  script = "javascripts/leaflet.timeline.js",
  stylesheet = "stylesheets/leaflet.timeline.css"
)

# use the new onRender in htmlwidgets to run
#  this code once our leaflet map is rendered
#  I did not spend time perfecting the leaflet-timeline
#  options
leaf %>%
  setView(44.0665,23.74667,2) %>%
  onRender(sprintf(
    '
function(el,x){
    var power_data = %s;

    var timeline = L.timeline(power_data, {
      pointToLayer: function(data, latlng){
        var hue_min = 120;
        var hue_max = 0;
        var hue = hue_min;
        return L.circleMarker(latlng, {
          radius: 10,
          color: "hsl("+hue+", 100%%, 50%%)",
          fillColor: "hsl("+hue+", 100%%, 50%%)"
        });
      },
      steps: 1000,
      duration: 10000,
      showTicks: true
    });
    timeline.addTo(HTMLWidgets.find(".leaflet"));
}
    ',
    power_geo
))
timelyportfolio
  • 6,479
  • 30
  • 33
  • 1
    timelyportfolio, you are a wizard and a true gentleman. I opted for `power$end <- power$start + 90` to actually see the markers and I will play around with this some more. Thank you very, very much. – Naibaf Apr 13 '16 at 09:30
  • @timelyportfolio. great answer. What would you do to make the colour conditional to a variable? – MLavoie May 26 '17 at 18:38
  • if I understand correctly you could add in the `sprintf` with `jsonlite::toJSON` a color map. So `var colors = %s` and then `jsonlite::toJSON(list(var1 = list(color = "...", fillColor="...", ...), auto_unbox = TRUE)` and lastly add the lookup in the JavaScript. Hard to explain in comment. – timelyportfolio May 26 '17 at 22:19
  • I have built a leaflet-timeline package [leaftime](https://github.com/timelyportfolio/leaftime) to make this easier. – timelyportfolio Nov 09 '19 at 19:24
13

An updated version of @timelyportfolio's post from last year. I needed to add a timelineControl function to get it to work. I also needed to call the getMap() function of the leaflet element to get the functions to bind to the map object.

library(htmlwidgets)
library(htmltools)
library(leaflet)
library(geojsonio)

#Build data.frame with 10 obs + 3 cols
power <- data.frame(
    "Latitude" = c(33.515556, 38.060556, 47.903056, 49.71, 49.041667, 31.934167, 54.140586, 54.140586, 48.494444, 48.494444),
    "Longitude" = c(129.837222, -77.789444, 7.563056, 8.415278, 9.175, -82.343889, 13.664422, 13.664422, 17.681944, 17.681944),
    "start" = do.call(
        "as.Date",
        list(
            x = c("15-Sep-1971", "1-Dec-1971", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Feb-1972", "1-Apr-1972", "1-Apr-1972", "24-Apr-1972", "24-Apr-1972"),
            format = "%d-%b-%Y"
        )
    )
)

# set start same as end
#  adjust however you would like
power$end <- power$start + 30


# use geojsonio to convert our data.frame
#  to GeoJSON which timeline expects
power_geo <- geojson_json(power,lat="Latitude",lon="Longitude", pretty = T)

# create a leaflet map on which we will build
leaf <- leaflet() %>%
    addTiles()

# add leaflet-timeline as a dependency
#  to get the js and css
leaf$dependencies[[length(leaf$dependencies)+1]] <- htmlDependency(
    name = "leaflet-timeline",
    version = "1.0.0",
    src = c("href" = "http://skeate.github.io/Leaflet.timeline/"),
    script = "javascripts/leaflet.timeline.js",
    stylesheet = "stylesheets/leaflet.timeline.css"
)

# use the new onRender in htmlwidgets to run
#  this code once our leaflet map is rendered
#  I did not spend time perfecting the leaflet-timeline
#  options
leaf %>%
    setView(44.0665,23.74667,2) %>%
    onRender(sprintf(
        '
        function(el,x){
        var power_data = %s;

        var timelineControl = L.timelineSliderControl({
          formatOutput: function(date) {
            return new Date(date).toString();
          }
        });

        var timeline = L.timeline(power_data, {
        pointToLayer: function(data, latlng){
        var hue_min = 120;
        var hue_max = 0;
        var hue = hue_min;
        return L.circleMarker(latlng, {
        radius: 10,
        color: "hsl("+hue+", 100%%, 50%%)",
        fillColor: "hsl("+hue+", 100%%, 50%%)"
        });
        },
        steps: 1000,
        duration: 10000,
        showTicks: true
        });
        timelineControl.addTo(HTMLWidgets.find(".leaflet").getMap());
        timelineControl.addTimelines(timeline);
        timeline.addTo(HTMLWidgets.find(".leaflet").getMap());
        }
        ',
        power_geo
    ))
conrad-mac
  • 843
  • 10
  • 15
  • @timelyportfolio: This is great but supposing you have a more complex data.frame, how do you indicate which variable holds the timeline information? Is there an argument for this in `geojson_json()`? – Amy M Jul 20 '17 at 18:26