3

JS leaflet allows two maps to be synchronized. See an example of synchronized leaflet maps here.

I would like to implement synchronized leaflet maps in R and more specifially in Rmarkdown/knitr.

Preferably, the maps should shown next to each other horizontally (just like in the example).

Here is a minimal Rmarkdown (.Rmd) example of two maps I would like to sync. The solution does not have to be based on the the mapview package. Any solution is welcome really (-:

---
title: "How to sync 2 leaflet maps"
author: "me"
date: "2 April 2016"
output: html_document
---

```{r SETUP, include=FALSE}
library("mapview")
library("sp")

# load example data
data(meuse)
coordinates(meuse) <- ~x+y
proj4string(meuse) <- CRS("+init=epsg:28992")
```

```{r MAPS}
mapView(meuse, zcol="copper")@map # MAP 1
mapview(meuse, zcol="soil")@map # MAP 2
```
fdetsch
  • 5,239
  • 3
  • 30
  • 58
chamaoskurumi
  • 2,271
  • 2
  • 23
  • 30
  • Unless I am wrong, I don't think it's been implemented in R leaflet or mapview. You will have to do it yourself in leaflet (http://leafletjs.com/). – MLavoie Apr 02 '16 at 15:53
  • As the maintainer of the `mapview` package, I can confirm that this is not currently available (though I haven't checked the latest version of leaflet on github for a while). Given that `mapview` is intended to aid spatial analysis workflow, may I ask what the purpose of the synchronised rendering is in your case? – TimSalabim Apr 02 '16 at 21:24
  • I want to visualize two different layers (lets call them variable X and Y) of spatial polygons to show there is a spatial correlation between the two. I want the reader to see that areas where X is high, exhibit high Y values as well and offer her/him the feature to zoom into specific areas - this is why static maps can't really do the job. By the way, I don't know JS. How would I implement that in JS or what `JS/html` code would I have to include in my `.Rmd` file? – chamaoskurumi Apr 04 '16 at 11:35
  • I will have a closer look at the `Leaflet.Sync` plugin and try to implement it in `mapview`. – TimSalabim Apr 04 '16 at 15:52

2 Answers2

4

Here is a way to sync the two leaflet maps, but unfortunately it does not work in RStudio Viewer. This does work in Chrome and Firefox. There are lots of ways to make this much more robust. I tried to add comments in the R code below to explain what is happening.

---
title: "How to sync 2 leaflet maps"
author: "me"
date: "2 April 2016"
output: html_document
---

```{r SETUP, include=FALSE}
#  get the latest htmlwidgets
#   devtools::install_github("ramnathv/htmlwidgets")
library("htmlwidgets")
library("htmltools")
library("mapview")
library("sp")

# load example data
data(meuse)
coordinates(meuse) <- ~x+y
proj4string(meuse) <- CRS("+init=epsg:28992")
```

```{r MAPS}
mapView(meuse, zcol="copper")@map # MAP 1
mapview(meuse, zcol="soil")@map # MAP 2
```

```{r}
#  crudely add the leaflet-sync plugin
#   attachDependency with the rawgit gave me
#   errors so just do this for now
#   could easily add to a package
#   or make a mini package to import this
#   dependency
tags$script(
  type="text/javascript",
  src="https://cdn.rawgit.com/turban/Leaflet.Sync/master/L.Map.Sync.js"
)
```

```{r}
# this is one of the new htmlwidgets methods
#  to add some code after all htmlwidgets are rendered
#  this is very useful since we need all htmlwidgets rendered
#  before we can sync
onStaticRenderComplete(
'
var leaf_widgets = Array.prototype.map.call(
  document.querySelectorAll(".leaflet"),
  function(ldiv){
    return HTMLWidgets.find("#" + ldiv.id);
  }
);

// make this easy since we know only two maps
leaf_widgets[0].sync(leaf_widgets[1]);
leaf_widgets[1].sync(leaf_widgets[0]);
'
)
```

Here is how we can do the same thing in straight R code.

#  http://stackoverflow.com/questions/36373842/synchronizing-two-leaflet-maps-in-r-rmarkdown

#  get the latest htmlwidgets
#   devtools::install_github("ramnathv/htmlwidgets")
library("htmlwidgets")
library("htmltools")
library("mapview")
library("sp")

# load example data
data(meuse)
coordinates(meuse) <- ~x+y
proj4string(meuse) <- CRS("+init=epsg:28992")
map1 <- mapView(meuse, zcol="copper")@map # MAP 1
map2 <- mapview(meuse, zcol="soil")@map # MAP 2

tagList(
  tags$head(tags$script(
    type="text/javascript",
    src="https://cdn.rawgit.com/turban/Leaflet.Sync/master/L.Map.Sync.js"
  )),
  map1,
  map2,
  onStaticRenderComplete(
'
var leaf_widgets = Array.prototype.map.call(
  document.querySelectorAll(".leaflet"),
  function(ldiv){
    return HTMLWidgets.find("#" + ldiv.id);
  }
);

// make this easy since we know only two maps
leaf_widgets[0].sync(leaf_widgets[1]);
leaf_widgets[1].sync(leaf_widgets[0]);
'
  )
) %>%
  browsable

And if you want it side-by-side, here is the basic way to accomplish. We could leverage shiny::fluidPage, fluidRow, and column to get boostrap, but the css/js is really heavy for just side-by-side placement.

#  get the latest htmlwidgets
#   devtools::install_github("ramnathv/htmlwidgets")
library("htmlwidgets")
library("htmltools")
library("shiny")
library("mapview")
library("sp")

# load example data
data(meuse)
coordinates(meuse) <- ~x+y
proj4string(meuse) <- CRS("+init=epsg:28992")
map1 <- mapView(meuse, zcol="copper")@map # MAP 1
map2 <- mapview(meuse, zcol="soil")@map # MAP 2

tagList(
  tags$head(tags$script(
    type="text/javascript",
    src="https://cdn.rawgit.com/turban/Leaflet.Sync/master/L.Map.Sync.js"
  )),
  tags$div(style="display:inline;width:50%;float:left;",map1),
  tags$div(style="display:inline;width:50%;float:left;",map2),
  onStaticRenderComplete(
'
var leaf_widgets = Array.prototype.map.call(
  document.querySelectorAll(".leaflet"),
  function(ldiv){
    return HTMLWidgets.find("#" + ldiv.id);
  }
);

// make this easy since we know only two maps
leaf_widgets[0].sync(leaf_widgets[1]);
leaf_widgets[1].sync(leaf_widgets[0]);
'
  )
) %>%
  browsable
timelyportfolio
  • 6,479
  • 30
  • 33
  • 1
    Thanks @timelyportfolio for this great answer!! However, the second example does not appear to be sync'ed. – TimSalabim Apr 11 '16 at 05:50
  • @timelyprotfolio is there a way to plot them side by side? – TimSalabim May 23 '16 at 12:52
  • Cool! Thanks! I would like to integrate this feature into the next **mapview** release (aiming at second week of June)... Would you be interested in contributing this as a function (maybe `syncView`) to the 'develop' branch? – TimSalabim May 23 '16 at 15:34
  • not sure I'll have time and don't want to hold you up. Feel free to use any of this code. I'll be happy to review/test if you get to it. – timelyportfolio May 24 '16 at 15:36
  • No problem. I 'll try my luck... Cheers – TimSalabim May 24 '16 at 17:34
  • That's a good idea. I have a basic version up already, but there are still some things I'd like to polish... – TimSalabim May 24 '16 at 19:14
  • @timelyportfolio new to htmlwidget, want to sync multiple leaflet widgets across different tabs of a standalone html (no shiny). tried your 1st section of code, maybe due to the library updates, it no longer works. or maybe I did it wrong. I just copy-pasted it and knit the document, opened the resulting html on a browser, the maps were not syncing. I want to make this work again, and find a way to run those JS code. anywhere I should look? – PaulDong May 27 '19 at 09:37
  • @timelyportfolio I realize it was because the CDN for the Leaflet Sync js library had been shut down. but even after I changed it to https://cdn.jsdelivr.net/npm/leaflet.sync@0.2.4/L.Map.Sync.js, it still wouldn't work. – PaulDong May 28 '19 at 01:45
  • does https://github.com/r-spatial/leafsync help? This is a package that wraps the above code in a more official way. – timelyportfolio May 31 '19 at 02:37
  • This does not work for me. `leaf_widgets[0]` does not have a `sync()` function. `leaf_widgets[0].getMap().sync(…)` works and I can sync two map objects via `leaf_widgets[0].getMap().sync(leaf_widgets[1].getMap())`. However, when I scroll the map, I get an error: `Uncaught TypeError: toSync._stop is not a function at L.Map.Sync.js:246` Any comments? – mzuba Jun 30 '20 at 08:01
2

Note, we have implemented the answer provided by @timelyportfolio in package mapview so that this is now easily achievable using mapview::sync(). See ?mapview::sync for instructions and examples.

TimSalabim
  • 5,604
  • 1
  • 25
  • 36