Sync two leaftlet maps on different tabs in RMarkdown with Shiny - r

Running into issues syncing two leaflet maps on different tabs.
After looking at previous entries (Synchronizing two leaflet maps in R / Rmarkdown), the solution provided by #TimSalabim does not work because the maps are on different tabs.
Here is a MWE RMarkdown example:
---
title: "QuestionforStackOverflow"
output:
flexdashboard::flex_dashboard:
runtime: shiny
---
```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(leaflet)
```
Tab One
======================================================================
```{r tab1}
output$map1 <-
renderLeaflet(
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(-93.65, 42.0285, zoom = 4)
)
leafletOutput("map1")
```
Tab Two
======================================================================
```{r tab2}
output$map2 <-
renderLeaflet(
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(-93.65, 42.0285, zoom = 4)
)
leafletOutput("map2")
```
I want a two way change. Any view changes to map1 -- changes map2 OR any changes to map2 will change map1.
Ideally: if you scroll into St. Louis on map1, map2 will have the same zoom level on St. Louis.
Right now, there is no interactivity between the two maps. Is there a way to make them sync?

You can use the leafletProxy() for this :
See the help here : https://rstudio.github.io/leaflet/shiny.html
For your particular problem, here's an idea :
---
title: "QuestionforStackOverflow"
output:
flexdashboard::flex_dashboard:
runtime: shiny
---
```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(leaflet)
```
Tab One
======================================================================
```{r tab1}
output$map1 <-
renderLeaflet(
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(-93.65, 42.0285, zoom = 4)
)
actionButton("syncMap1", "Fit to map2 bounds")
leafletOutput("map1")
observeEvent(input$syncMap1,{
map2coords <- input$map2_bounds
map1Proxy <- leafletProxy("map1")
map1Proxy %>% fitBounds(lng1 = map2coords$east,
lat1 = map2coords$north,
lng2 = map2coords$west,
lat2 = map2coords$south)
})
```
Tab Two
======================================================================
```{r tab2}
output$map2 <-
renderLeaflet(
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(-93.65, 42.0285, zoom = 4)
)
actionButton("syncMap2", "Fit to map1 bounds")
leafletOutput("map2")
observeEvent(input$syncMap2,{
map1coords <- input$map1_bounds
map2Proxy <- leafletProxy("map2")
map2Proxy %>% fitBounds(lng1 = map1coords$east,
lat1 = map1coords$north,
lng2 = map1coords$west,
lat2 = map1coords$south)
})
```
The idea is to retrieve the coordinates of the other map when clicking on the buttons, and to sync the view then.
Minor problem : the view is not that well synced : could be better to find the centroid of the displayed map and to use setView() using input$map1_zoom.
Major problem : this means using buttons, what is not that user-friendly.
Theoritically, you could use an observe() block to reflect the coordinates of each map to the other. Tried it and it's quite buggy, probably because of some "infinite" loop as there are micro-variations in the coordinates.

No idea how that would work with R, but with plain JavaScript you could use Leaflet.Sync to synchronize two maps.
If you have two tabs of which only one is visible at the time, you could also just sync on tab switch, which is much simpler to implement.

Related

How to add Leaflet.Locate to an R made map (instead of locate)

I was looking into this repo, where a blue dot is added to your leaflet map, that if you open it on a phone, it follows you as you walk, similar to a google maps option, as shown in this demo.
Usually in R and using Rmd, I would have this code so that I can know where I am on the map:
title: "Test map"
output: html_document
knitr::opts_chunk$set(echo = TRUE)
library(leaflet)
Old map.locate option
I usually made this maps for the field
leaflet() %>%
addTiles() %>%
setView(-71.0382679, 42.3489054, zoom = 18) %>%
addEasyButton(easyButton(
icon="fa-crosshairs", title="Locate Me",
onClick=JS("function(btn, map){ map.locate({setView: true, enableHighAccuracy: true }); }")))
That works great for finding your location, but it does not generate a marker where you are, and more importantly, it does not follow you around, you can see an example of that here
Trying to incorporate control.locate
So my first try was just to change locate for control.locate, but that did not work.
leaflet() %>%
addTiles() %>%
setView(-71.0382679, 42.3489054, zoom = 18) %>%
addEasyButton(easyButton(
icon="fa-crosshairs", title="Follow Me",
onClick=JS("function(btn, map){ map.control.locate()}")))
I am still thinking of other options, but any help would be welcome, here is the full rmd in github
These GPS features have been implemented in the leaflet.extras package.
Here's a working version based on your MWE
library(leaflet)
library(leaflet.extras)
your_map <- leaflet() %>%
addTiles() %>%
setView(-71.0382679, 42.3489054, zoom = 18) %>%
addControlGPS(
options = gpsOptions(
position = "topleft",
activate = TRUE,
autoCenter = TRUE,
setView = TRUE))
activateGPS(your_map)
The results looks like this:
Spoiler: Chrome thinks I'm in São Paulo right now... (where I'd rather be!)
...and here's a working demo on my Git.
Work's like a charm on my mobile.

How to insert saved Leaflet widget into Rmarkdown HTML output

I create a Leaflet widget and save it locally:
library(htmlwidgets)
library(leaflet)
library(sf)
shp = st_read("/path/to/some/shapefile.shp")
m = shp %>%
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(lng = -70, lat = 40, zoom = 11)
saveWidget(m, "m.html")
Now I want to load this widget in a Rmarkdown chunk:
---
title: "Title"
author: "author"
date: "5/8/2020"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
etc etc etc
```{r}
function_that_loads_widget("m.html")
```
etc etc etc
I've tried htmltools::includeHTML() but that makes the entire HTML output one big widget. The text of the report is not displayed.
I realize I could put the the code that created the Leaflet widget directly in the Rmarkdown chunk, but I don't want to do that.
knitr::include_url() appears to be the solution. This works for my blogdown post.
```{r, out.width="100%"}
knitr::include_url("url_of_html", height="1080px")
```

R Shiny document: gap when conditional map not displayed

I am producing a Shiny interactive document. I wish to give the user the option of displaying a map conditional on checkbox status, and that map has some reactive content. I am able to achieve this, but not without the space that the map occupies remaining when it is not displayed. I believe this is also true of a plot instead of a map.
Is it possible to have the map's absence leave no gap?
---
title: "Conditional Map"
runtime: shiny
output: html_document
---
```{r setup, include=FALSE, results='hide'}
knitr::opts_chunk$set(echo = FALSE)
library(leaflet)
library(shiny)
```
The map should be present based upon the condition of the checkbox:
```{r}
# coordinates for markers:
Coords = list("London"=c(0,51), "New York" = c(-74,40))
selectInput(inputId = "Loc",label = "Select location", choices = names(Coords))
checkboxInput(inputId = "ShowMap", label="Show map?", value=TRUE)
leafletOutput("Map")
output$Map = renderLeaflet({if(input$ShowMap) leaflet() %>% addTiles %>%
setView(-45,45,zoom=2) %>%
addMarkers(lng=Coords[[input$Loc]][1],lat=Coords[[input$Loc]][2])})
```
## The Next Bit
Some additional content here which should appear directly below the previous content, whether that is the map or the checkbox.
Use conditional panel like so:
conditionalPanel(condition = "input.ShowMap == true",
leafletOutput("Map"))

Leaflet map tiles not visible in reveal.js presentation within R?

Intro:
I am trying to embed a leaflet map into a revealjs presentation within an RMarkdown file. My example below is pretty close, but it is (1) missing tiles, (2) the popups are not displaying, and (3) the legend and font are way too big!
I am not too concerned with how the code blocks look at the moment. I'm planning on using the results = "hide" slide option for my final product.
Thanks in advance!
Reproducible Example:
---
title: "My Presentation"
author: Me
date: 2017-06-23
output:
revealjs::revealjs_presentation:
theme: black
---
## Loading in necessary packages:
```{r}
library(dplyr)
library(sp)
library(rgdal)
library(rgeos)
library(RColorBrewer)
library(classInt)
library(leaflet)
library(htmlwidgets)
```
## Defining our data:
```{r}
lat <- c(45.51158000, 45.50431159, 45.496539)
lon <- c(-122.548056, -122.54775, -122.54788)
no2 <- c(17.37, 25.61, 24.69)
dta <- data.frame(lat, lon, no2)
colnames(dta) <- c("lat","lon","no2")
```
## Create layer of spatial points:
```{r}
points <- SpatialPointsDataFrame(data.frame(x=dta$lon, y=dta$lat), data = data.frame(dta$no2))
plotclr <- (brewer.pal(7, "RdYlGn"))
class <- classIntervals(dta$no2, n = 7, style = "fixed", fixedBreaks = c(0,5,10,15,20,25,30))
colcode <- findColours(class, rev(plotclr))
plot(points, col=colcode, pch=19)
pop1<-paste0("<b>NO2:</b> ", dta$no2, " ppb",
"<br /> <b>Lat:</b> ", dta$lat,
"<br /> <b>Lon:</b> ", dta$lon)
```
## Creating the leaflet map:
```{r}
no2_map <-leaflet()%>%
addTiles('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png') %>%
addCircleMarkers(data=points, color = "black", radius = dta$no2, fillColor = colcode, fillOpacity=0.7, weight=1, popup=pop1) %>%
addLegend(position = "bottomright", colors = rev(plotclr), labels = rev(c("30","25","20","15","10","5","0")), opacity = 0.9, title = "NO2 (ppb)")
```
---
```{r}
no2_map
saveWidget(no2_map, file="map.html")
```
Unfortunately, reveal.js and Leaflet don't play very well together, and the slide with your maps might be missing layers. This is due to Leaflet not being able to discern the size of the DOM element which serves as the container of the map, because reveal.js resizes all elements dinamically.
The easiest workaround is to just refresh the page when you're in a slide with a Leaflet map. You can also try a deferred call to map.invalidateSize() (by using setTimeout() in plain Javascript)

Add Polygons to R shiny leaflet map

How do I add polygons from Global Administrative areas, so they are clickable.
The simple way describe in the docs that I tried is
adm <- getData('GADM', country='UKR', level=1)
leaflet() %>% addTiles() %>% addPolygons(data=adm, weight = 3, fillColor = col)
But imagine I want a leaflet map that will have onClick actions later.
Based on SuperZip, I need to have something similar to
map <- createLeafletMap(session, "map")
session$onFlushed(once=TRUE, function() {
map$addPolygon(...)
})
However, there is no addPolygon method and I am confused how will it work for SpartialPolygons.
I also tried converting to geoJSON, similar to https://ropensci.org/blog/2013/10/23/style-geojson-polygon/ or this SO question, but doing
polys <- fromJSON(<json data file>)
map <- createLeafletMap(session, "map")
session$onFlushed(once=TRUE, function() {
map$geoJson(polys)
})
Gives me an error
Error in func() : attempt to apply non-function
Is there a way to do it? Or what am I doing wrong?
I am not sure I really understand the problem, although I read through the question a couple of times. However the code below seems to work for me, as it can easily be combined with a simple onClick event, like a pop up displaying the name of each adm. unit:
---
title: "Ukraine"
runtime: shiny
output: html_document
---
```{r, echo=FALSE, message=F, warning=F}
library(leaflet)
library(raster)
adm <- getData('GADM', country='UKR', level=1)
popup <- paste0("<strong>Name: </strong>",
adm$NAME_1)
leaflet() %>%
addTiles() %>%
addPolygons(data=adm, weight = 2, fillColor = "yellow", popup=popup)
```

Resources