This is the first time I try to reorganize a shiny app into shiny modules, so, some help would be welcome.
My goal is to create a clickable map module with leaflet that would store the latitude and longitude in an input that I could re-use in other modules. Currently the module works by creating in the ui a leafletOutput (id="mymap") and using in the server function a observeEvent function that reacts to click on the map. The click event generates an input vector of the longitude and latitude (input$input$mymap_click$lat[1] & input$mymap_click$lng[1])which is used to place a marker on the map. But I struggle to extract those to values to use it externally by other modules or render* functions. (it works without the "module method" but the code is a bit messy)
For the sake of clarity in my example I try to use the latitude and longitude in a textOutput with renderText instead of in a module.
# clickable leaflet module ----------------------------------------------------------
## loads leaflet library
library(leaflet)
##ui function
clicMapOutput <- function(id) {
ns <- NS(id)
tagList(leafletOutput(ns("mymap")),
textOutput(ns("text")))
}
## serverfunction
clicMapServer <- function(id) {
moduleServer(id,
function(input, output, session) {
# outputs a map
output$mymap <-
leaflet::renderLeaflet({
leaflet() %>% addTiles() %>% setView(lat = 0,
lng = 0,
zoom = 2)
})
# makes map clickable to obtain a marker and a longitude + latitude vector
observeEvent(input$mymap_click, {
output$mymap <-
leaflet::renderLeaflet({
leaflet() %>% addTiles() %>% addMarkers(lat = input$mymap_click$lat[1],
lng = input$mymap_click$lng[1])
})
})
})
}
# Calling modules ---------------------------------------------------------
library(shiny)
ui<-fluidPage(
clicMapOutput("map"),
textOutput("lng")
)
server<-function(input,output,session){
clicMapServer("map")
output$lng<-renderText({
input$mymap_click$lng[1]
})
}
shinyApp(ui=ui,server=server)
the ususal way of doing this is to define a return value in the server part of the module and then using it in the module consumer
## module server
clicMapServer <- function(id) {
moduleServer(id, function(input, output, session) {
## ...
return(reactive(input$mymap_click$lng[1])))
}
}
## consumer server
server <- function(input, output, session) {
lng <- clicMapServer("map")
output$lng <- renderText({ lng() })
}
You should always make sure to wrap the return values with reactives and use the return value like a function. If you want to return more than one variable, see my answer to this question for details.
library(leaflet)
library(shiny)
## module ui
clicMapOutput <- function(id) {
ns <- NS(id)
leafletOutput(ns("mymap"))
}
## module server
clicMapServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>% addTiles() %>% setView(
lat = 0, lng = 0, zoom = 2)
})
# handle click events
observeEvent(input$mymap_click, {
output$mymap <- renderLeaflet({
leaflet() %>% addTiles() %>% addMarkers(
lat = input$mymap_click$lat[1],
lng = input$mymap_click$lng[1])
})
})
return(reactive(input$mymap_click$lng[1]))
})
}
# main ui
ui <- fluidPage(
clicMapOutput("map"),
textOutput("lng")
)
# main server
server <- function(input, output, session) {
lng <- clicMapServer("map")
output$lng <- renderText({ lng() })
}
shinyApp(ui = ui, server = server)
Another thing I noticed is that you are updating the leaflet widget by overwriting output$mymap. It would be better to use leaflet::leafletProxy() instead. Generally speaking, outputs should not be assigned inside observe() or observeEvent()
Related
I am very new to shiny and the mapping function.
Based on the code of the link bellow, I did a simple one, which does not work.
Can someone tell me why is not working?
There is no error shown. However, it doesn't show the map.
I am working with the dataset of NYC bikes.
library(shiny)
library(leaflet)
bikes <- read.csv("Data/201501-citibike-tripdata.csv")
ui <- fluidPage(
leafletOutput("mymap")
)
server <- function(input, output) {
output$mymap <- renderLeaflet({
leaflet(bikes) %>%
setView(lng = -73.98928, lat = 40.75042, zoom = 10) #NYC
})
}
shinyApp(ui=ui, server = server)
link: http://rstudio-pubs-static.s3.amazonaws.com/133599_c0d5471268584d47b53298f0ad27e8d3.html
I'm using leaflet to plot a map and to get coordinates from user click.
Next, I want to save these coordinates in a global reactive list, so I can use these coordinates in another module. The problem is that I can't access the reactive list when it's placed on the server function (in app.R escope), neither outside the server function. The only way i found is to put the list of reactives inside the server function on the module server, so obviously is not global anymore.
How should I proceed? I want something like this (code below), so I can use the value saved in 'r' in all modules within the same session (not shared between sessions).
Map Module
mapModuleUI <- function(id){
ns <- NS(id)
leafletOutput(ns('map'), height="600px")
}
mapModuleServer <- function(input, output, session, r){
# MAP plot #####
output$map = renderLeaflet({
leaflet(width = '50 px', height = '50 px') %>% addTiles() %>% setView(-41.65, -22.0285, zoom = 10)
})
# # MAP LOGIC
observe({
click <- input$map_click
leafletProxy('map') %>% removeMarker('pointClicked')
if (is.null(click)){return()}
leafletProxy('map') %>% addMarkers(lng = click$lng, lat = click$lat, layerId = 'pointClicked')
r$lat <- click$lat %>% formatC(digits = 2, format = 'f')
r$lon <- click$lng %>% formatC(digits = 2, format = 'f')
})
} # end server function
app.R
ui <- dashboardPage(skin = 'green',
....
tabItem(tabName = "newGr",
box(mapModuleUI('mapa'))
)
...
)
server <- function(input, output, session) {
r <- reactiveValues()
callModule(mapModuleServer, 'mapa')
}
shinyApp(ui, server)
Leaflet provides an option, when setting up your map, to hide the zoom controls
leaflet(options = leafletOptions(zoomControl = FALSE)
However, I would like to call this option after having already created a map so that a user can download the map without the zoom controls and without me having to re-create a different version of the map from scratch.
Here's a simple version of my app at the moment:
library(shiny)
library(tidyverse)
library(leaflet)
library(mapview)
ui <- fluidPage(
fluidPage(
leafletOutput(outputId = "map"),
downloadButton(outputId = "save")
)
)
server <- function(input, output, session) {
map <- reactive({
leaflet() %>%
addTiles()
})
output$map <- renderLeaflet({
map()
})
output$save <- downloadHandler(
filename = "map.jpeg",
content = function(file){
latRng <- range(input$map_bounds$north,
input$map_bounds$south)
lngRng <- range(input$map_bounds$east,
input$map_bounds$west)
map() %>%
setView(lng = (lngRng[1] + lngRng[2])/2,
lat = (latRng[1] + latRng[1])/2,
zoom = input$map_zoom) %>%
### HERE ###
mapshot(file = file)
}
)
}
shinyApp(ui, server)
I'd like to be able to add a line of code where I've commented ### HERE ### that would turn off zoom controls. In my actual code the displayed map is really complex with lots of options and I wouldn't want to have all that code twice just for the sake of removing zoom controls in the initial call to leaflet().
Thanks
You can do it like so:
library(shiny)
library(tidyverse)
library(leaflet)
library(mapview)
ui <- fluidPage(
fluidPage(
leafletOutput(outputId = "map"),
downloadButton(outputId = "save")
)
)
server <- function(input, output, session) {
map <- reactive({
leaflet() %>%
addTiles()
})
output$map <- renderLeaflet({
map()
})
output$save <- downloadHandler(
filename = "map.jpeg",
content = function(file){
latRng <- range(input$map_bounds$north,
input$map_bounds$south)
lngRng <- range(input$map_bounds$east,
input$map_bounds$west)
m = map() %>%
setView(lng = (lngRng[1] + lngRng[2])/2,
lat = (latRng[1] + latRng[1])/2,
zoom = input$map_zoom)
m$x$options = append(m$x$options, list("zoomControl" = FALSE))
mapshot(m, file = file)
}
)
}
shinyApp(ui, server)
which is updating the leaflet options after map creation. I will incorporate this in the mapshot function to optionally remove the zoomControl.
I have a shiny app with a large sf of lines
I would like to view it only at zoom 15 and to load only the visible part
library(leaflet)
library(shiny)
library(dplyr)
library(sf)
#random multilinestring
polyline_test <-st_multilinestring()
ui <- fluidPage(
leafletOutput("map")
)
server <- function(input, output, session){
output$map <- renderLeaflet({
leaflet() %>%
addTiles()
})
##Only select data visible on the map
new_zoom <- reactive({
if(!is.null(input$map_zoom)){
new_zoom <- input$map_zoom
}
else {new_zoom <- 2}
})
polyline_visible <- reactive({
validate(
need(new_zoom()>15,"t")
)
#bbox_zone <- input$map_bounds
#st_polygon()
#polyline_test
#What is the best way to extract the lines within the bounds ?
})
#Show layer only at zoom 15
observe({
if (new_zoom() > 15) {
leafletProxy("map") %>%
addPolylines(data = polyline_visible())
}else{
leafletProxy("map") %>% clearShapes()
}
})
}
shinyApp(ui = ui, server = server)
I would like to know the best way to extract the lines within the bounds of the visible map?
I am trying to create a leaflet Shiny app however I keep getting the Warning: Error in derivePoints: addMarkers requires non-NULL longitude/latitude values Error. I have attached the code herewith. Also, a screenshot of the input data files and links to download.
DataBooks.csv
GPSBook.csv
Code:
library(shiny)
library(leaflet)
Location_levels=list(Institutional=0, Provincial=1, National=2, International=3)
DataBook <- read.csv("~/R_Projects/TNL_Network/DataBook.csv", comment.char="#")
GPSBook <- read.csv("~/R_Projects/TNL_Network/GPSBook.csv", comment.char="#")
## Create content for the popups in the markers
popUpContent <- function(ins_id){
subs<-subset(DataBook, Institute_id==ins_id)
name <- subs$Institute[[1]]
return(name[[1]])
}
## Get unique markers based on the location type selected. This function calls the popup content function above and returns a dataframe
markerData <- function(location){
subs1<-subset(DataBook, Location_level<=Location_levels[location])
unique_ins_ids<-levels(factor(subs1$Institute_id))
mdata.list <- vector("list", length(unique_ins_ids))
for(i in 1:length(unique_ins_ids)){
mdata.list[[i]] <- list(subset(GPSBook, Institute_id==unique_ins_ids[i])["Longitude"][[1]], subset(GPSBook, Institute_id==unique_ins_ids[i])["Latitude"][[1]],
as.character(popUpContent(unique_ins_ids[i])))
}
solution <- do.call('rbind', mdata.list)
dataf<-data.frame(solution)
colnames(dataf)<-c("lat", "long", "Msg") ## I ihave mixed up the origincal longitude and latitude. I invert it here.
return(dataf)
}
## Function to create initial data.
initData <- function(){
return(markerData("International"))
}
init_dataset <- initData()
ui <- fluidPage(
leafletOutput("mymap"),
p(),
radioButtons(inputId = "radio", label = "", choices = as.list(levels(DataBook$Location)), selected = "International")
)
server <- function(input, output, session) {
observe({
proxy <- leafletProxy("mymap", data = markerData(input$radio))
proxy %>% clearMarkers()
proxy %>% addMarkers()
})
output$mymap <- renderLeaflet({
leaflet(data = markerData(input$radio)) %>% addTiles() %>%
addMarkers()
})
}
shinyApp(ui, server)
Thanks a lot for the help.
Links to files.
https://drive.google.com/open?id=0B-TWCTRv7UM1bnVpWEIxTnB2d28
https://drive.google.com/open?id=0B-TWCTRv7UM1cjBxNnlhR2ZXc0U
I hope I have understood you intention. If yes this can be simplified a lot.
This is how I would do it. (just change back to the correct directories where your csv files are). The code:
library(shiny)
library(leaflet)
DataBook <- read.csv("./data/DataBook.csv", comment.char="#")
GPSBook <- read.csv("./data/GPSBook.csv", comment.char="#")
names(GPSBook) <- names(GPSBook)[c(1,2,4,3)]
ui <- fluidPage(
leafletOutput("mymap"),
p(),
radioButtons(inputId = "radio", label = "", choices = as.list(levels(DataBook$Location)), selected = "International")
)
server <- function(input, output, session) {
location <- reactive({
tmp <- subset(DataBook, Location_level <= Location_levels[input$radio])
uniqueIds <- unique(tmp$Institute_id)
tmpGps <- subset(GPSBook, Institute_id %in% uniqueIds)
})
observe({
proxy <- leafletProxy("mymap", data = location())
proxy %>% clearMarkers()
proxy %>% addMarkers(popup = ~as.character(Name))
})
output$mymap <- renderLeaflet({
leaflet(data = GPSBook) %>% addTiles() %>%
addMarkers(popup = ~as.character(Name))
})
}
shinyApp(ui, server)
In your original code the function was creating a list so the data was not prepared as leaflet would expect them to be.