Adding multiple layers to leaflet map in Shiny in R - r

I am having trouble adding different layers in my shiny app. I want to add a group of polygons along with a group of circle markers along with a group of arbitrary (.png) icons. I have the group of geojson files that are added in a for loop that is wrapped in an observe({}) statement with the function
map$addGeoJSON(x) where x is a feature with coordinates. The 'map' object is created by the command
map <- createLeafletMap(session, 'map')
This is all fine and dandy, and the polygons get added fine. I also want to commit to this way of adding the polygons. That should not have to change.
The error happens when I try to add markers onto that map object in the same way (e.g. with map$addMarkers(....) ) Below is the error and the code for the app that tries to add markers in the desired way and fails.
The shiny app below with the quakes data recreates my error
"Listening on ...
Warning: Error in observerFunc: attempt to apply non-function
Stack trace (innermost first):
56: observerFunc [C:/Users/jbz/Desktop/leaflet-map-question.R#35]
1: runApp
ERROR: [on_request_read] connection reset by peer"
library(shiny)
library(leaflet)
library(RColorBrewer)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletMap("map", width = "100%", height = "100%",
options=list(center = c(40.736, -73.99), zoom = 14)),
absolutePanel(top = 10, right = 10,
sliderInput("range", "Magnitudes", min(quakes$mag), max(quakes$mag),
value = range(quakes$mag), step = 0.1
),
selectInput("colors", "Color Scheme",
rownames(subset(brewer.pal.info, category %in% c("seq", "div")))
),
checkboxInput("legend", "Show legend", TRUE)
)
)
server <- function(input, output, session) {
filteredData <- reactive({
quakes[quakes$mag >= input$range[1] & quakes$mag <= input$range[2],]
})
colorpal <- reactive({
colorNumeric(input$colors, quakes$mag)
})
map <- createLeafletMap(session, 'map')
observe({
df <- filteredData()
map$addMarkers(
lng=df$Lon, lat=df$Lat, popup = paste(as.character(df$mag)))
})
}
shinyApp(ui, server)
(How) can I add markers correctly while insisting on using the function createLeafletMap()?
map <- createLeafletMap(session, 'map')

try:
library(dplyr)
df <- filteredData()
leafletProxy("map") %>%
addMarkers(df, lng = ~Lon, lat = ~Lat, popup = paste(as.character(df$mag) )
under observe

Related

Drawing Polygons with leaflet in shiny?

I have a geospatial dataset of monthly average temperatures in the US. I want to display this as a leaflet map in a Shiny app. With a time-slider, users should be able to select a visualisation of each month.
When I try to run my data with codes I found online I run into a number of problems and unfortunately I don't understand exactly where which data is needed.
On Wetransfer I uploaded my dataset Data.
Relevant info about the dataset: I want the slider to run by either the "Valid_Seas" column (monthly values by parts of the US) or "values". The polygons (column: Geometry) should be colored by the column "Prob", this is the monthly average temperature.
Regarding the R.skript: Starting at line 215 is my attempt to create the ShinyApp map, just a you can see here:
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
absolutePanel(top = 10, right = 10,
style="z-index:500;", # legend over my map (map z = 400)
tags$h3("Average Temperature"),
sliderInput("periode", "Months 2021",
min(tempyear21$values),
max(tempyear21$values),
value = range(tempyear21$values),
step = 1,
sep = ""
)
)
)
#bis hier hin stimmt es
server <- function(input, output, session) {
# reactive filtering data from UI
reactive_data_chrono <- reactive({
tempyear21 %>%
filter(Valid_Seas >= input$periode[1] & Valid_Seas <= input$periode[2])
})
# static backround map
output$map <- renderLeaflet({
leaflet(tempyear21) %>%
addTiles() %>%
fitBounds(-49.57,24.91,-166.99,68.00)
})
# reactive circles map
observe({
leafletProxy("map", data = reactive_data_chrono()) %>%
clearShapes() %>%
addMarkers(lng=~lng,
lat=~lat,
layerId = ~id) # Assigning df id to layerid
})
}
shinyApp(ui, server)
I am very much looking forward to any advice. Kind regards
Pernilla
I spotted three problems with your code. First, your input slider returns number(s), while your data set column Valid_Seas is character ("Jan 2021", etc.). Hence, after you apply filter the dataset is reduced to zero rows. Better use the values column instead.
Second, if you wanted to display month by month, you should pass only one single number as value argument to sliderInput, like
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
absolutePanel(top = 10, right = 10,
style="z-index:500;", # legend over my map (map z = 400)
tags$h3("Average Temperature"),
sliderInput("periode", "Months 2021",
min(tempyear21$values),
max(tempyear21$values),
value = min(tempyear21$values), # !
step = 1,
animate=TRUE, # add play button
sep = ""
)
)
)
Otherwise, you get an overlay of several months.
Third problem: your dataset has polygons, in your server function you use addMarkers. You need to use addPolygons instead. In order to fill the polygons, you need to determine a color for each number. The classInt and RColorBrewer packages can help you with that:
library(classInt)
library(RColorBrewer)
n <- 3 # number of categories
pal <- RColorBrewer::brewer.pal(n, "Reds")
ivar <- classInt::classIntervals(
tempyear21$Prob, n=n, style="quantile"
)
tempyear21$colcode <- classInt::findColours(ivar, pal)
legend_names <- names(attr(tempyear21$colcode, "table"))
As for the server function, I think you are on the right track with leafletProxy.
server <- function(input, output, session) {
# static map elements
output$map <- renderLeaflet({
leaflet() |> addTiles() |>
fitBounds(-49.57,24.91,-166.99,68.00) |>
addLegend(position="topleft", colors=pal, labels=legend_names)
})
# map handler
map_proxy <- leafletProxy("map", session)
# react on slider changes
observeEvent(input$periode, {
dat <- subset(tempyear21, values == input$periode)
map_proxy |> leaflet::clearShapes() |>
leaflet::addPolygons(
data=dat,
weight=1,
color=dat$colcode, # border
opacity=1,
fillColor=dat$colcode
)
})
}

Zoom into Leaflet Map Based on User Inputs

I am trying to figure out how to zoom into a map based on user inputs. I have started with the map of the US and then trying zooming into a specific location based on zip code and number of miles:
shinyUI(fluidPage(
# Application title
titlePanel("Starbucks Locator"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
textInput("zip","Zip Code:", value = "18101"),
sliderInput("radius",
"Radius",
min = 1,
max = 25,
value = 15),
actionButton("go", "Submit")
),
# Show a plot of the generated distribution
mainPanel(
leafletOutput("myMap")
)
)
))
This is the Server Code:
library(shiny)
library(ZipRadius)
library(leaflet)
shinyServer(function(input, output) {
data <- reactive({x <- readRDS("Starbucks.rds")})
output$myMap <- renderLeaflet({
df <- data()
m <- leaflet(data = df) %>%
addTiles() %>%
addMarkers(lng = ~Lon, lat = ~Lat)
})
eventReactive(input$go, {
zip_include <- zipRadius(input$zip, input$radius)
})
leafletProxy("myMap") %>% fitBounds(~min(zip_include$longitude), ~min(zip_include$latitude),
~max(zip_include$longitude), ~max(zip_include$latitude))
})
I am using the ZipRadius package to find the which zip codes are input$distance away from input$zip. Then I want to zoom into the input zip code as the center and the bounds defined by the radius (input$distance). The original leaflet map works, but the leafletProxy is where I am having my issue. Any thoughts on how to do this? I am trying to use fitbounds since it can take into account minimum and maximum latitudes and longitudes. I think the issue may be with the eventReactive, but not sure how to test it. The dataset is available here at github.
There are a few issues with your server code.
You don't need to create your data in a reactive expression. As you only upload it once (I guess you won't change the coords or add another row).
Your renderLeaflet function can be simplified
Instead of using eventReactive use observeEvent. This function is triggered when you press the go button. In the observer you have to add all the actions that should be triggered by the button click. So you also have to insert the leafletProxy function here.
Remove the ~ symbol in the fitBounds function. You are already pointing to the zip_include dataset in fitBounds.
Working server (with provided csv)
shinyServer(function(input, output) {
data <- read.csv("c:/starbucks_us_locations.csv")
names(data) <- c('Lon', 'Lat')
output$myMap <- renderLeaflet({leaflet(data) %>%
addTiles() %>%
addMarkers(lng = ~Lon, lat = ~Lat)
})
observeEvent(input$go, {
zip_include <- zipRadius(input$zip, input$radius)
print(zip_include)
leafletProxy("myMap") %>% fitBounds(min(zip_include$longitude), min(zip_include$latitude),
max(zip_include$longitude), max(zip_include$latitude))
})
})

Modularizing R Shiny code: ObserveEvent function in module

I am trying to improve the usability of my app.R code in R Shiny which is getting very long.
Essentially, I'd like to create a module (infras.R) to contain a large number of observeEvent functions that are linked to checkboxInputs.
I understand I need to source the module in app.R, wrap the observeEvent in a function, include namespaces (ns) for input IDs in the observeEvent function and insert a callModule for the function. I've also wrapped the callModule in an ObserveEvent so that its functionality persists and does not trigger only once after starting the webapp.
The following error is output on running app.R but I'm not sure how to resolve:
Warning: Error in proxy: could not find function "proxy"
81: eval
80: eval
79: %>%
78: module [infras.R#153]
73: callModule
72: observeEventHandler
1: runApp
Thanks for your assistance with this as I've found it challenging to find literature on how to do this.
Key snippets from my R scripts.
infras.R (updated):
icons_pow <- awesomeIcons(
iconColor = 'white',
markerColor = 'green',
text = "m"
)
mod <- function(input, output, session, pow_id, prox){
observeEvent(pow_id(),{
if(pow_id() != 0){
pow_id <- readOGR("../geospatial_files/ind", layer = "plants")
pow_iddf <- as.data.frame(pow_id)
prox %>%
addAwesomeMarkers(lng=pow_iddf$coords.x1, lat=pow_iddf$coords.x2, group = "pow_idg", icon=icons_pow,
label = paste(pow_iddf$Name,pow_iddf$Power_type,sep = ", "))
}
else {prox %>% clearGroup("pow_idg") %>% removeControl(layerId="pow_idc")
}
}
)
}
app.R (updated):
...
source("infras.R")
...
server <- function(input, output, session) {
...
proxy <- leafletProxy("map")
callModule(mod, "mod", reactive(input$pow_id), proxy)
})
...
}
You need to wrap your input object into a reactive and use that as an input argument to your module. The other input argument is your leaflet proxy. Inside the module, you can use observe to change your proxy, which is then instantly updated:
library(shiny)
library(leaflet)
library(RColorBrewer)
# The module containing the observer. Input is the reactive handle of legend input and the proxy
mod <- function(input, output, session, legend, prox){
observe({
prox %>% clearControls()
if (legend()) {
prox %>% addLegend(position = "bottomright",
pal = colorNumeric("Blues", quakes$mag), values = ~mag
)
}
})
}
ui <- bootstrapPage(
checkboxInput("legend", "Show legend", TRUE),
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
pal <- colorNumeric("Blues", quakes$mag)
leaflet(quakes) %>% addTiles() %>%
addCircles(radius = ~10^mag/10, weight = 1, color = "#777777",
fillColor = ~pal(mag), fillOpacity = 0.7, popup = ~paste(mag)) %>%
fitBounds(~min(long), ~min(lat), ~max(long), ~max(lat))
})
# This is the handle for map
proxy <- leafletProxy("map", data = quakes)
callModule(mod, "mod", reactive(input$legend), proxy)
}
shinyApp(ui, server)

problems with select input in r shiny leaflet app

I am using R 3.2.3 through RStudio Version 0.99.491, on Windows 10 64bit... I am creating a leaflet shiny app, using graduated circlemarkers. I want to display different months to show the change in data using selectInput(), but i don't know how to connect it to the 'radius =' argument of addCirclemarker() to make it dynamic. I know I'm just making it up with the 'radius =' argument of addCirclemarker() but I can't tell if I have selectInput() wrong too. here's the data I'm using. The result shows no error message and the map worked when the radius argument was given a single column assignment, ie a static map.
ui.r:
library(shiny)
library(leaflet)
shinyUI(fluidPage(
titlePanel("CAT Rider Count Map"),
sidebarLayout(
sidebarPanel(
selectInput("var", label = "1. Select the Month",
choices = c("April" = 3, "May" = 4, "June" = 5),
selected = 4)),
mainPanel(leafletOutput('crossact.map')
))))
server.r
library(shiny)
library(googlesheets)
library(leaflet)
gs_auth()
ttt <- gs_auth()
saveRDS(ttt, "ttt.rds")
gs_auth(token = ttt)
gs_auth(token = "ttt.rds")
crossact <- gs_title("crossact")
crossact <- crossact%>% gs_read_csv()
shinyServer(
function(input, output, session){
colm <- reactive({
as.numeric(input$var)
})
output$crossact.map <- renderLeaflet({
##################################################################
#RADIUS SECTION
##################################################################
crossact.map <- leaflet(crossact) %>%
addTiles('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png')
crossact.map%>% setView(-71.43381, 42.48649, zoom = 17)
crossact.map %>% ***addCircleMarkers(~lng, ~lat, popup=~crossact$name, weight =1,
radius=~(crossact[,colm()]),
color="#ffa500", stroke = TRUE, fillOpacity = 0.6)
})
})
thanks!
For the solution to my specific problem, I used code from the superzip app, for anyone making leaflet shiny apps with markers, this seems to have it all.
http://shiny.rstudio.com/gallery/superzip-example.html (hit the Get Code button and it will send you to Github)
Correct me if I'm wrong, but, sizeBy <- input$size pull the values from the choice argument, and is the bridge to the selectInput() function. radius <- crossact[[sizeBy]] assigns the overlapping strings from the data.frame object to the selectInput() variable sizeBy by making the variable radius. For this to work, the map function must have an observer({}) wrapper to have it update itself when the selection changes.
ui.r
library(shiny)
library(leaflet)
#this is the assignment of columns to the choices argument in selectinput()
vars <- c(
"April" = "April",
"May" = "May",
"June" = "June")
shinyUI(fluidPage(
h5("Integrating Leaflet With Shiny"),
titlePanel("CAT Rider Count Map"),
sidebarLayout(
sidebarPanel(
selectInput("size", "Size", vars, selected = "April")),
mainPanel(leafletOutput('crossact.map')
))))
Server.r
library(shiny)
library(googlesheets)
library(leaflet)
#google authorization, token storage, file acquisition and assignment
gs_auth()
ttt <- gs_auth()
saveRDS(ttt, "ttt.rds")
gs_auth(token = ttt)
gs_auth(token = "ttt.rds")
crossact <- gs_title("crossact")
crossact <- crossact%>% gs_read_csv()
shinyServer(
function(input, output, session){
####observer is used to maintain the circle size.
observe({
#####this connects selectInput and assigns the radius value
sizeBy <- input$size
radius <- crossact[[sizeBy]]
output$crossact.map <- renderLeaflet({
crossact.map <- leaflet(crossact) %>%
addTiles('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png')
crossact.map%>% setView(-71.43381, 42.48649, zoom = 17)
crossact.map %>% addCircleMarkers(~lng, ~lat, popup=~crossact$name, weight = 1,radius = radius,
color="#ffa500", stroke = TRUE, fillOpacity = 0.6)
})
})
})

How to change title text color in Leaflet for R?

I'm trying to change the style / color of the heading for the slider title "Magnitudes", but I can't figure out what to do. I've tried adding things like
p {color: red} to the tags$style line, like this:
tags$style(type = "text/css", "html, body {width:100%;height:100%}", "p {color=white}"),
to no avail. Any ideas? I don't think it's something you change in the actual sliderInput function itself, but rather CSS, I just can't quite figure it out.
library(shiny)
library(leaflet)
library(RColorBrewer)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
absolutePanel(top = 10, right = 10,
sliderInput("range", "Magnitudes", min(quakes$mag), max(quakes$mag),
value = range(quakes$mag), step = 0.1
),
selectInput("colors", "Color Scheme",
rownames(subset(brewer.pal.info, category %in% c("seq", "div")))
),
checkboxInput("legend", "Show legend", TRUE)
)
)
server <- function(input, output, session) {
# Reactive expression for the data subsetted to what the user selected
filteredData <- reactive({
quakes[quakes$mag >= input$range[1] & quakes$mag <= input$range[2],]
})
# This reactive expression represents the palette function,
# which changes as the user makes selections in UI.
colorpal <- reactive({
colorNumeric(input$colors, quakes$mag)
})
output$map <- renderLeaflet({
# Use leaflet() here, and only include aspects of the map that
# won't need to change dynamically (at least, not unless the
# entire map is being torn down and recreated).
leaflet(quakes) %>% addTiles() %>%
fitBounds(~min(long), ~min(lat), ~max(long), ~max(lat))
})
# Incremental changes to the map (in this case, replacing the
# circles when a new color is chosen) should be performed in
# an observer. Each independent set of things that can change
# should be managed in its own observer.
observe({
pal <- colorpal()
leafletProxy("map", data = filteredData()) %>%
clearShapes() %>%
addCircles(radius = ~10^mag/10, weight = 1, color = "#777777",
fillColor = ~pal(mag), fillOpacity = 0.7, popup = ~paste(mag)
)
})
# Use a separate observer to recreate the legend as needed.
observe({
proxy <- leafletProxy("map", data = quakes)
# Remove any existing legend, and only if the legend is
# enabled, create a new one.
proxy %>% clearControls()
if (input$legend) {
pal <- colorpal()
proxy %>% addLegend(position = "bottomright",
pal = pal, values = ~mag
)
}
})
}
shinyApp(ui, server)
Cutting straight to the chase:
Try adding this to your ui:
tags$style(type = "text/css", 'label[for="range"] {color: white;}'),
More detail about how you might figure that out on your own:
Here's how I'd proceed.
Use runApp() to run the code you've got, producing a very nice leaflet map in your browser.
Right-click on that map and select "View Page Source" to see the source code that's producing the map.
Search that source for the string "Magnitude", to find the HTML element that codes for the title you'd like to whiten. Here's what I find when I do that:
<label class="control-label" for="range">Magnitudes</label>
From that, construct a CSS selector (here including an "attribute selector") that'll find that element, and use it to change the color. Adding this, following the first line of your call to bootstrapPage(), does the trick for me:
tags$style(type = "text/css", 'label[for="range"] {color: white;}'),
Use runApp() again to confirm that the edit worked.

Resources