shiny return data based on click - r

I have a global database of grid ID, lat and long at 100-km resolution and associated
data for each 100-km grid. Something like this
grid_ID Lat Lon data1 data2 data3 ... data4
I want to develop a shiny app that allows the user to:
click anywhere on the map
return corresponding lat and lon
based on the returned lat and lon, find which 100-km grid does it fall into
show the data associated with that 100-km grid as a pop-up window or as a table (either will do)
library(shiny)
library(leaflet)
library(shinythemes)
ui <- fluidPage(theme = shinytheme('superhero'),
leafletOutput("map"),
verbatimTextOutput("out")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>% addProviderTiles('Esri.WorldImagery')
})
output$out <- renderPrint({
validate(need(input$map_click, FALSE))
str(input$map_click)
})
}
shinyApp(ui, server)
This is now returning lat and long of the location that I clicked on which is my
step 1 and 2. How do I go about doing step 3 and 4. For step 3, usually if it was not an shiny app, I would do
coordinates(dat) <- ~ Longitude + Latitude
proj4string(dat) <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
GRID <- over(dat, grid_shp)
where grid_shp is my global 100-km grid shapefile and dat has the returned lat lon from the click.

Probably not the most elegant solution and also not sure if it's in leaflet for R, but you could define each grid rectangle as a polygon, loop through the polygons and do if(Polygon.getBounds().contains(LatLng) == true); then you know which grid square you're in. Then return the feature properties of the grid square aka polygon object.

I assume your dataset is called "data". You have to find the grid which intersects with the coordinates of the click.
library(shiny)
library(leaflet)
library(shinythemes)
ui <- fluidPage(theme = shinytheme('superhero'),
leafletOutput("map"),
verbatimTextOutput("out"))
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>% addProviderTiles('Esri.WorldImagery')
})
output$out <- renderPrint({
validate(need(input$map_click, FALSE))
str(input$map_click)
# create a dataframe from the clicked point
xy <-
data.frame(longitude = input$map_click$lng,
latitude = input$map_click$lat)
# transform this dataframe to spatialpointsdataframe
spdf <-
SpatialPointsDataFrame(
coords = xy,
data = xy,
proj4string = CRS("+proj=longlat +datum=WGS84 +no_defs")
)
# find the grid that intersects with the point
selected_raster <- spdf %over% data
# here you will see the information you need
as.data.frame(selected_raster)
})
}
shinyApp(ui, server)

Related

Combine date slider, radio button, and map in Shiny

I want to create a map that displays traffic KPIs by date and location. The user is able to select a day of traffic with a slider, and a traffic KPI with radio buttons. The data is not showing up on the map.
I have created a reactive object that filters the data based on radio button and slider. The code to render the LeafLet map works outside the app, showing the circles for the data.
The data frame is structured as follows:
date,lat,long,pageviews,unique_visitors
01.01.2019,6.7304,-3.49,206,238
04.01.2019,7.1604,3.35,223,275
07.01.2019,52.25,-4.25,272,407
10.01.2019,46.9757,-123.8095,44,448
13.01.2019,45.4646,-98.468,98,269
16.01.2019,35.1351,-79.432,443,337
19.01.2019,39.5146,-76.173,385,21
22.01.2019,57.1704,-2.08,273,371
25.01.2019,18.2301,42.5001,115,195
28.01.2019,5.32,-4.04,7,27
31.01.2019,32.4543,-99.7384,217,136
03.02.2019,38.923,-97.2251,337,15
06.02.2019,2.7017,33.6761,201,390
09.02.2019,36.7089,-81.9713,177,201
12.02.2019,30.1204,74.29,65,82
15.02.2019,5.4667,-3.2,261,229
18.02.2019,7.1904,1.99,364,38
21.02.2019,3.9837,13.1833,131,74
24.02.2019,-22.7167,-65.7,357,198
27.02.2019,39.4228,-74.4944,297,399
02.03.2019,24.4667,54.3666,382,147
05.03.2019,34.4504,40.9186,8,373
08.03.2019,9.0833,7.5333,83,182
11.03.2019,-9.6954,-65.3597,243,444
14.03.2019,16.85,-99.916,420,29
-> It's stored under "joined" outside of the app (I'm joining two tables) and I call it at the beginning of the pipeline in the reactive object
When I select the date and metric, the output is structured as follows:
lat,long,selected_metric
lat is latitude and long is longitude
I guess the issue is how I'm calling the dataframe in renderLeaflet, as it is a reactive object I'm not sure if the ~ command works to call the columns.
# Required packages
library(shiny)
library(leaflet)
library(dplyr)
# Define UI for application that shows a map
ui <- fluidPage(
# App title
titlePanel("Metrics by location"),
# Input: select date range
sliderInput("traffic_date",
"Date:",
min = as.Date("2019-01-01","%Y-%m-%d"),
max = as.Date("2019-07-31","%Y-%m-%d"),
value=as.Date("2019-07-31"),
timeFormat="%Y-%m-%d"),
# Input: select metric
radioButtons("metric",
"Metric",
c("Pageviews" = "pageviews",
"Unique Visitors" = "unique_visitors"),
selected = "pageviews"),
# Main panel for Output
mainPanel(
# Output: map
leafletOutput("mymap")
)
)
# Define server commands to draw map with data
server <- function(input, output) {
# Reactive expression to generate dataframe for selected date and metric
d <- reactive({
day <- input$traffic_date
show_metric <- input$metric
d <- joined %>%
filter(date == day) %>%
select(lat,long,show_metric) %>%
rename(selected_metric = show_metric)
})
# Note: the last pipeline element renames the metric column back to a neutral name
#create the map
output$mymap <- renderLeaflet({
leaflet(d()) %>%
addTiles() %>%
setView(8.36,46.84,7) %>%
addCircles(lat = ~ lat,
lng = ~ long,
weight = 1,
radius = ~ selected_metric)
})
}
# Run app
shinyApp(ui, server)
Currently the code returns an empty map, and I'm not sure which step I'm missing to display the circles.
Thank you for the help!
I think your issue is the use of radius. See below taken from the help documentation:
radius
a numeric vector of radii for the circles; it can also be a one-sided formula, in which case the radius values are derived from the data (units in meters for circles, and pixels for circle markers)
I realised the markers were there they were just really small. Try multiplying the selected_metric by 10000 or changing to use addCircleMarkers.
Update
Using your data set which I converted to date and numeric where applicable and removing setView() so that the map automatically zooms to points out of that range. One of the issues I had was I initially couldn't see points as they were in Africa for example. Also many dates within the range above don't have data to display circles.
# Required packages
library(shiny)
library(leaflet)
library(dplyr)
# Define UI for application that shows a map
ui <- fluidPage(
# App title
titlePanel("Metrics by location"),
# Input: select date range
sliderInput("traffic_date",
"Date:",
min = as.Date("2019-01-01","%Y-%m-%d"),
max = as.Date("2019-07-31","%Y-%m-%d"),
value=as.Date("2019-01-01"),
timeFormat="%Y-%m-%d"),
# Input: select metric
radioButtons("metric",
"Metric",
c("Pageviews" = "pageviews",
"Unique Visitors" = "unique_visitors"),
selected = "pageviews"),
# Main panel for Output
mainPanel(
# Output: map
leafletOutput("mymap")
)
)
# Define server commands to draw map with data
server <- function(input, output) {
# Reactive expression to generate dataframe for selected date and metric
d <- reactive({
day <- input$traffic_date
show_metric <- input$metric
d <- joined %>%
filter(date == day) %>%
select(lat,long,show_metric) %>%
rename(selected_metric = show_metric)
})
# Note: the last pipeline element renames the metric column back to a neutral name
#create the map
output$mymap <- renderLeaflet({
leaflet(d()) %>%
addTiles() %>%
# setView(8.36,46.84,7) %>%
addCircles(lat = ~ lat,
lng = ~ long,
weight = 1,
radius = ~ selected_metric)
})
}
# Run app
shinyApp(ui, server)

infoBox/valueBox from shinyDashboard in shiny

I have a simple shiny-app with just a dropdown listing districts of Afghanistan and a leaflet map of the same.
The shape file can be accessed at this link - using AFG_adm2.shp from http://www.gadm.org/download
here's the app code:
library(shiny)
library(leaflet)
library(rgdal)
library(sp)
afg <- readOGR(dsn = "data", layer ="AFG_adm2", verbose = FALSE, stringsAsFactors = FALSE)
ui <- fluidPage(
titlePanel("Test App"),
selectInput("yours", choices = c("",afg$NAME_2), label = "Select Country:"),
leafletOutput("mymap")
)
server <- function(input, output){
output$mymap <- renderLeaflet({
leaflet(afg) %>% addTiles() %>%
addPolylines(stroke=TRUE, color = "#00000", weight = 1)
})
proxy <- leafletProxy("mymap")
observe({
if(input$yours!=""){
#get the selected polygon and extract the label point
selected_polygon <- subset(afg,afg$NAME_2==input$yours)
polygon_labelPt <- selected_polygon#polygons[[1]]#labpt
#remove any previously highlighted polygon
proxy %>% removeShape("highlighted_polygon")
#center the view on the polygon
proxy %>% setView(lng=polygon_labelPt[1],lat=polygon_labelPt[2],zoom=7)
#add a slightly thicker red polygon on top of the selected one
proxy %>% addPolylines(stroke=TRUE, weight = 2,color="red",data=selected_polygon,layerId="highlighted_polygon")
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
I want a infoBox or valueBox like widget from shinyDashboard to display some data(like district population) below the map based on user selection. How can I do this?
You can mimic the shinydashboard::infoBox with your own function:
create function
myInfoBox <- function(title, value)
{
div(
div(class='myinfobox-title', title),
div(class='myinfobox-value', value)
)
}
use uiOutput() whenever you want to place it e.g. uiOutput('idOfInfoBox')
in server part use e.g. output$idOfInfoBox <- renderUI(myInfoBox(title, value)
add .css file in www/ directory and add some properties for classes myinfobox-title and myinfobox-value
I hope this helps.
You need to change the structure of the program and need to add dashboard page in UI.
Here are some reference just have a look. you will get to know!!!
https://rstudio.github.io/shinydashboard/structure.html
https://rdrr.io/cran/shinydashboard/man/valueBox.html

Using reactiveValues() inside an observe function in Shiny Leaflet app causes infinite recursion

I have a Shiny Leaflet app I'm working on that saves map click events with the use of reactiveValues() (that question was posted by me here). When I use observeEvent for each click event on the map, the reactiveValues work perfectly.
In my full app code, it has become necessary to use observe instead of observeEvent. When I make this change from observeEvent to observe, the reactiveValues seem to cause infinite recursion. Here's some reproducible example code:
library(raster)
library(shiny)
library(leaflet)
#load shapefile
rwa <- getData("GADM", country = "RWA", level = 1)
shinyApp(
ui = fluidPage(
leafletOutput("map")
),
server <- function(input, output, session){
#create empty vector to hold all click ids
clickedIds <- reactiveValues(ids = vector())
#initial map output
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = rwa,
fillColor = "white",
fillOpacity = 1,
color = "black",
stroke = T,
weight = 1,
layerId = rwa#data$OBJECTID,
group = "regions")
}) #END RENDER LEAFLET
observe({
#create object for clicked polygon
click <- input$map_shape_click
#append all click ids in empty vector
clickedIds$ids <- c(clickedIds$ids, click$id)
print(clickedIds$ids)
}) #END OBSERVE EVENT
}) #END SHINYAPP
I'm able to stop this by using unique() or isolate() in the following line of code:
#append all click ids in empty vector
clickedIds$ids <- c(unique(clickedIds$ids), click$id)
BUT in my full app, this (for whatever reason) slows the entire app and makes some visualizations clunky. I'm looking for some alternatives and ideally an explanation why observe causes infinite recursion whereas observeEvent does not.

Broken images marker on leaflet map

Good afternoon everyone, I think a screenshot will tell better than words:
I would like a real marker instead of what is actually printed...
Here is the corresponding server.R part of the code (where fixed_points is a nrow(fixed_points) x 2 matrix with lat/lon coordinates
library(shiny); library(rCharts); library(leaflet)
output$baseMap <- renderMap({
baseMap <- Leaflet$new()
baseMap$setView(c(lng = 2.812500, lat = 46.732331), zoom = 6)
baseMap$tileLayer(provider = "Stamen.TonerLite")
for (i in 1:nrow(fixed_points)) {baseMap$marker(c(fixed_points[i, "lat"], fixed_points[i, "lon"]))}
baseMap$fullScreen(TRUE)
baseMap
})
and the ui.R
showOutput("baseMap", "leaflet") // or chartOuput(...), it works the same
Thanks in advance!

Identify position of a click on a raster in leaflet, in R

I am plotting a large lat-lon NetCDF raster over an R leaflet map using shinydashboard. When I click on the map, a popup comes out and shows row, column, lat-lon position and value of the clicked raster point. (See reproducible code below)
The problem is that I am experiencing a shift in the raster if the raster is large enough. For example, here I clicked on a point which should have a value, but the result is that the identified point is the one above.
I believe this has to do with the fact that the raster used by leaflet is projected, while the raw data I use to identify the points is Lat-Lon, since the clicked point is returned as Lat-Lon by leaflet. I cannot use the projected file (depth) since its units are in meters, not degrees!
Even if I tried to reproject those meters to degrees, I got a shift.
Here is a basic runnable example of the code:
#Libraries
library(leaflet)
library(raster)
library(shinydashboard)
library(shiny)
#Input data
download.file("https://www.dropbox.com/s/y9ekjod2pt09rvv/test.nc?dl=0", destfile="test.nc")
inputFile = "test.nc"
inputVarName = "Depth"
lldepth <- raster(inputFile, varname=inputVarName)
lldepth[Which(lldepth<=0, cells=T)] <- NA #Set all cells <=0 to NA
ext <- extent(lldepth)
resol <- res(lldepth)
projection(lldepth) <- "+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"
#Project for leaflet
depth <- projectRasterForLeaflet(lldepth)
#Prepare UI
sbwidth=200
sidebar <- dashboardSidebar(width=sbwidth)
body <- dashboardBody(
box( #https://stackoverflow.com/questions/31278938/how-can-i-make-my-shiny-leafletoutput-have-height-100-while-inside-a-navbarpa
div(class="outer",width = NULL, solidHeader = TRUE, tags$style(type = "text/css", paste0(".outer {position: fixed; top: 50px; left: ", sbwidth, "px; right: 0; bottom: 0px; overflow: hidden; padding: 0}")),
leafletOutput("map", width = "100%", height = "100%")
)
)
)
ui <- dashboardPage(
dashboardHeader(title = "A title"),
sidebar,
body
)
#
#Server instance
server <- function(input, output, session) {
output$map <- renderLeaflet({#Set extent
leaflet() %>%
fitBounds(ext[1], ext[3], ext[2], ext[4])
})
observe({#Observer to show Popups on click
click <- input$map_click
if (!is.null(click)) {
showpos(x=click$lng, y=click$lat)
}
})
showpos <- function(x=NULL, y=NULL) {#Show popup on clicks
#Translate Lat-Lon to cell number using the unprojected raster
#This is because the projected raster is not in degrees, we cannot use it!
cell <- cellFromXY(lldepth, c(x, y))
if (!is.na(cell)) {#If the click is inside the raster...
xy <- xyFromCell(lldepth, cell) #Get the center of the cell
x <- xy[1]
y <- xy[2]
#Get row and column, to print later
rc <- rowColFromCell(lldepth, cell)
#Get value of the given cell
val = depth[cell]
content <- paste0("X=",rc[2],
"; Y=",rc[1],
"; Lon=", round(x, 5),
"; Lat=", round(y, 5),
"; Depth=", round(val, 1), " m")
proxy <- leafletProxy("map")
#add Popup
proxy %>% clearPopups() %>% addPopups(x, y, popup = content)
#add rectangles for testing
proxy %>% clearShapes() %>% addRectangles(x-resol[1]/2, y-resol[2]/2, x+resol[1]/2, y+resol[2]/2)
}
}
#Plot the raster
leafletProxy("map") %>%
addRasterImage(depth, opacity=0.8, project=FALSE, group="Example", layerId="Example", colors=colorNumeric(terrain.colors(10), values(depth), na.color = "black"))
}
print(shinyApp(ui, server))
How can I correctly identify the points, if the raster is large?
EDIT:
I also wanted to provide some additional links to (possibly) related documentation or questions:
Raster image seems to be shifted using leaflet for R
R for leaflet redirect when clicking on raster image
https://gis.stackexchange.com/questions/183918/is-it-possible-to-use-a-rasterclick-event-within-an-interactive-leaflet-map
marker mouse click event in R leaflet for shiny
I have found that I can reproject back the X-Y (lon-lat) position given by input$map_click.
In this case I assumed the input projection to be Lon-Lat, but I think it doesn't necessarily have to be. It just needs to have Lat-Lon units.
#Set projections
inputProj <- "+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"
leafletProj <- "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +units=m +nadgrids=#null +wktext +no_defs"
#Note that for some reason "+nadgrids=#null +wktext" is very important
#as hinted to by other questions and answers linked in my question.
xy <- SpatialPoints(data.frame(x,y))
proj4string(xy) <- inputProj
xy <- as.data.frame(spTransform(xy, leafletProj))
#Get the cell number from the newly transformed metric X and Y.
cell <- cellFromXY(depth, c(xy$x, xy$y))
#At this point, you can also retrace back the center of the cell in
#leaflet coordinates, starting from the cell number!
xy <- SpatialPoints(xyFromCell(depth, cell))
proj4string(xy) <- leafletProj
xy <- as.data.frame(spTransform(xy, inputProj))
#Here XY will again be in lat-lon, if you projection says so,
#indicating the center of the clicked cell

Resources