Shiny, Event reactive, running several functions - r

I am new in shiny, at the moment I am trying to set up a code where I can calculate activity clusters (through DBSCAN package) based on input variables: "eps" (minimum distances between points to be part of a cluster), "minpts" (minimum number of points to certain categories as Health), "maxpts" (minimum number of points for general categories as pubs, restaurants etc).
I did a test only through leaflet (without shiny) and the code runs smoothly, but once I bring-in shiny, I'm not able to make it work
the idea is that the user can modify these 3 variables on the side panel, and click an action button in order to trigger the calculation.
#----------LIBRARIES----------#
library(plyr)
library(geosphere)
library(dbscan)
library(osmdata)
library(sf)
library(tidyr)
library(sp)
library(rgdal)
library(leaflet)
library(shiny)
#-------LOAD FILES-------#
OSM_merged <- read.csv(file = "C:\\Users\\jsainz\\Documents\\R\\Shiny_test\\OSM_merged.csv")
OSM_points <- OSM_merged
OSM_points$color <- OSM_points$category
OSM_points$color <- str_replace_all(OSM_points$color, "Culture", "#3073A")
OSM_points$color <- str_replace_all(OSM_points$color, "Educational", "# 887CAF")
OSM_points$color <- str_replace_all(OSM_points$color,"Financial", "#540002")
OSM_points$color <- str_replace_all(OSM_points$color,"Health", "#D6E899")
OSM_points$color <- str_replace_all(OSM_points$color,"Leisure", "#D2D68D")
OSM_points$color <- str_replace_all(OSM_points$color,"Office", "#D3696C")
OSM_points$color <- str_replace_all(OSM_points$color,"Shop", "#AA9739")
OSM_points$color <- str_replace_all(OSM_points$color,"Sport", "#378B2E")
OSM_points$color <- str_replace_all(OSM_points$color,"Sustain", "#554600")
OSM_points$color <- str_replace_all(OSM_points$color,"Toursim", "#5FAE57")
xy <- OSM_points[,c(2,3)]
OSM_points <- SpatialPointsDataFrame(coords = xy, data = OSM_points,proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
#-------FUNCTIONS-------#
assign_clusters <- function(poi_df, minPts = NA) {
if(is.na(minPts)) {
if(poi_df[1, "category"] %in% c("Culture", "Leisure", "Education", "Health", "Financial")) {
minPts <- "minpts"
} else minPts <- "maxpts"
}
eps <- "epsilon"
poi_df[c("lng", "lat")] %>%
distm(fun = distHaversine) %>%
as.dist() %>%
dbscan(eps = eps, minPts = minPts) %>%
.[["cluster"]] %>%
cbind(poi_df, cluster = .)
}
get_hull<- function(df) {
cbind(df$lng, df$lat) %>%
as.matrix() %>%
st_multipoint() %>%
st_convex_hull() %>%
st_sfc(crs = 4326) %>%
{st_sf(category = df$category[1], cluster = df$cluster[1], geom = .)}
}
hulls <- function(df) {
df %>%
split(.$cluster) %>%
map(get_hull)
}
#----------SHINY CODE----------#
ui <- fluidPage(
titlePanel("Jorge_Test"),
sidebarPanel(
numericInput(inputId = "epsilon", label = "distance in meters to calculate activity clusters", 200),
numericInput(inputId = "minpts", label = "minimum points to calculate clusters", 5),
numericInput(inputId = "maxpts", label = "maximum points to calculate clusters", 10),
actionButton("run", "Run Calculation"),
actionButton("view", "generate plan"),
width = 2),
mainPanel(
leafletOutput("mymap", width = 1550, height = 850)
)
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet("mymap")%>%
setView(lng = 0.0982, lat = 51.7674, zoom = 15)%>%
addProviderTiles(providers$CartoDB.Positron, options = providerTileOptions(noWrap = TRUE))%>%
addCircleMarkers(data = OSM_points,
radius = .7,
popup = ~category,
color = ~color)})
oberveEvent(input$run, {
updateNumericInput(session, "epsilon")
updateNumericInput(session, "minpts")
updateNumericInput(session, "maxpts")
})
Clean_data <- OSM_merged %>%
split(OSM_merged$category) %>%
map_df(assign_clusters)
hulls_cat <- Clean_data %>%
group_by(category) %>%
summarise()
map_cluster_hulls <- Clean_data %>%
filter(cluster != 0) %>%
select(lng, lat, category, cluster) %>%
split(.$category) %>%
map(hulls)
mdata <- melt(map_cluster_hulls, id = c("category", "cluster", "geom"))
mch <- data.frame(mdata$category, mdata$cluster, mdata$geom)
observeEvent(input$view, {
leafletProxy("mymap", session) %>%
addPolygons(data = mch$geom,
fill = NA,
fillOpacity = .01,
weight = 2,
color = "red",
opacity = .8)
}
)
}
shinyApp(ui, server)
any idea of how to solve it?
here is a link to the OSM_merged.csv file:
https://www.dropbox.com/s/5ok9frcvx8oj16y/OSM_merged.csv?dl=0

Related

Using click events in leaflet to dynamically display grouped sums

I'm working on a shiny app with basic functionality like this:
library(sf)
library(DT)
library(leaflet)
library(shiny)
library(tidyverse)
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = T) %>%
st_transform(4326) %>%
select(NAME, geometry, id = CNTY_ID) %>%
mutate(x = rnorm(n = nrow(.), mean = 100, sd = 20),
fill = sample(c("green", "red"), n(), replace = T),
fill_2 = if_else(fill == "green", "red", "green"))
# Function to change fill color on click event (taken from https://stackoverflow.com/a/69618323)
change_color <- function(map, id_to_remove, data, colour, new_group){
leafletProxy(map) %>%
removeShape(id_to_remove) %>% # remove previous occurrence
addPolygons(
data = data,
layerId = data$id,
group = new_group, # change group
fillColor = colour,
color = "black",
weight = 1,
fillOpacity = 1)
}
## UI
ui <- fluidPage(
leafletOutput("map"),
DT::dataTableOutput("table")
)
## Server
server <- function(input,output,session){
# Reactives
rv <- reactiveValues(
df = nc,
df.tab = as.data.frame(nc)
)
# Initial map
output$map <- renderLeaflet({
leaflet() %>%
setView(-79.99, 35.52, zoom = 7)
})
observe({
data <- rv$df
leafletProxy("map") %>%
addPolygons(
data = data,
weight = 1, color = "black", fillOpacity = 1, fillColor = ~fill,
layerId = data$id,
group = "unclicked_poly")
})
#first click
observeEvent(input$map_shape_click, {
# execute only if the polygon has never been clicked
req(input$map_shape_click$group == "unclicked_poly")
# filter data
data <- rv$df[rv$df$id==input$map_shape_click$id,]
change_color(map = "map",
id_to_remove = input$map_shape_click$id,
data = data,
colour = ~fill_2,
new_group = "clicked1_poly")
})
#second click: reverse first click
observeEvent(input$map_shape_click, {
req(input$map_shape_click$group == "clicked1_poly")
data <- rv$df[rv$df$id==input$map_shape_click$id,]
leafletProxy("map") %>%
removeShape(input$map_shape_click$id) %>% # remove previous occurrence
addPolygons(
data = data,
weight = 1, color = "black", fillOpacity = 1, fillColor = ~fill,
layerId = data$id,
group = "unclicked_poly") # back to initialize group
})
output$table <- DT::renderDataTable({
rv$df.tab %>%
group_by(fill) %>%
summarise(x = sum(x))
})
}
shinyApp(ui, server)
The idea here is that the user can change the fill color of the polygons with the click of a button. This works as is. However, I also want to dynamically display the fill-specific sum of x in the data tabe below the leaflet map. Currently, the table shows the grouped sums according to the initial data frame. However, when a user changes a polygon from green to red, the calculation should be done anew.
I have tried implementing this idea using a logic similar to the observeEvents() in output(map), but the problem here was that I could only ever access the last click, so previous clicks would not factor into the grouped sums calculation (group_by(fill) %>% summarise(x = sum(x))). Ideally, I would like to have information on whatever the current fill of all polygons is so that the data table reflects the user's input.
I ended up solving this problem in four steps:
Recording each click on a polygon using reactiveValues(Clicks=vector())
Converting vector into data frame, with click frequency determined by table()
Using modulo division on the number of clicks with the %% operator to ascertain current fill color on map (the number of fill options is much higher than two in my real world application)
Merging clicked and unclicked polygons to obtain current map status and using DT::dataTableProxy() to update table
App is now working as intended. Code:
library(sf)
library(DT)
library(leaflet)
library(shiny)
library(tidyverse)
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = T) %>%
st_transform(4326) %>%
select(NAME, geometry, id = CNTY_ID) %>%
mutate(x = rnorm(n = nrow(.), mean = 100, sd = 20),
fill = sample(c("green", "red"), n(), replace = T),
fill_2 = if_else(fill == "green", "red", "green"))
# Function to change fill color on click event (taken from https://stackoverflow.com/a/69618323)
change_color <- function(map, id_to_remove, data, colour, new_group){
leafletProxy(map) %>%
removeShape(id_to_remove) %>% # remove previous occurrence
addPolygons(
data = data,
layerId = data$id,
group = new_group, # change group
fillColor = colour,
color = "black",
weight = 1,
fillOpacity = 1)
}
## UI
ui <- fluidPage(
leafletOutput("map"),
DT::dataTableOutput("table")
)
## Server
server <- function(input,output,session){
# Reactives
rv <- reactiveValues(
df = nc,
df.tab = as.data.frame(nc)
)
# Initial map
output$map <- renderLeaflet({
leaflet() %>%
setView(-79.99, 35.52, zoom = 7)
})
observe({
data <- rv$df
leafletProxy("map") %>%
addPolygons(
data = data,
weight = 1, color = "black", fillOpacity = 1, fillColor = ~fill,
layerId = data$id, label = ~id,
group = "unclicked_poly")
})
#first click
observeEvent(input$map_shape_click, {
# execute only if the polygon has never been clicked
req(input$map_shape_click$group == "unclicked_poly")
# filter data
data <- rv$df[rv$df$id==input$map_shape_click$id,]
change_color(map = "map",
id_to_remove = input$map_shape_click$id,
data = data,
colour = ~fill_2,
new_group = "clicked1_poly")
})
#second click: reverse first click
observeEvent(input$map_shape_click, {
req(input$map_shape_click$group == "clicked1_poly")
data <- rv$df[rv$df$id==input$map_shape_click$id,]
leafletProxy("map") %>%
removeShape(input$map_shape_click$id) %>% # remove previous occurrence
addPolygons(
data = data,
weight = 1, color = "black", fillOpacity = 1, fillColor = ~fill,
layerId = data$id, label = ~id,
group = "unclicked_poly") # back to initialize group
})
output$table <- DT::renderDataTable({
rv$df.tab %>%
group_by(fill) %>%
summarise(x = sum(x)) -> sum
sum
})
proxy <- DT::dataTableProxy("table")
RV<-reactiveValues(Clicks=vector())
observeEvent(input$map_shape_click, {
#create object for clicked polygon
click <- input$map_shape_click
RV$Clicks<- c(RV$Clicks,click$id)
test <- as.data.frame(table(RV$Clicks)) %>%
mutate(current = Freq %% 2,
id = as.double(as.character(Var1)))
rv$df.tab %>%
full_join(test, by = "id") %>%
mutate(fill = case_when(current == 1 ~ fill_2,
TRUE ~ fill)) %>%
group_by(fill) %>%
summarise(x = sum(x)) -> sum
proxy %>% replaceData(sum)
})
}
shinyApp(ui, server)

Hide and display multiple edges from process_map() using selectInput()

I have the shiny app below in which I create a process map. What I want to do is subset this process map based on the transitions selectInput().
All the transitions can be seen from the obect edges which I extract from the process_map() object at the beginning but then how can I pass the selected from the selectInput() again to the process_map() object?what I acually need is to hide/display the edges between the nodes if deselect/select one transition pair.
This is how I make it work but I cannot make it work for multiple selection ,using multiple=T inside the selectInput().
library(shiny)
library(bupaR)
library(svgPanZoom)
library(DiagrammeRsvg)
library(DiagrammeR)
library(processmapR)
edges <- patients %>% process_map(performance(mean, "days"))
edges <- attr(edges, "edges")
colnames(edges)[1]<-"predecessor"
colnames(edges)[2]<-"successor"
graph <- process_map(patients
, type_nodes = frequency("absolute",color_scale = "Greys")
,type_edges = frequency("absolute",color_edges = "Greys"),
rankdir = "LR", render = FALSE)
ui <-shinyUI(fluidPage(
selectInput("tran","transitions"
,choices = c("All",paste(edges$predecessor,"-",edges$successor)),
#multiple=T
,selected = "All"),
svgPanZoomOutput("pmap",height = 500,width = 1600)
))
server <- function(input, output) {
output$pmap <- renderSvgPanZoom({
req(input$tran)
if (input$tran != "All"){
pre <- strsplit(input$tran, " - ")[[1]][[1]]
suc <- strsplit(input$tran, " - ")[[1]][[2]]
#creating copy of graph for processing
ndf = get_node_df(graph)
edf = get_edge_df(graph)
newg = create_graph(nodes_df = ndf, edges_df = edf)
newg$global_attrs <- graph$global_attrs
#Finding edges to remove based on pre/suc nodes, selecting edge, removing
#using startWith due termination chars being added
from_nodes = newg %>% clear_selection() %>%
select_nodes(conditions = startsWith(tooltip,pre)) %>% get_selection()
to_nodes = newg %>% clear_selection() %>%
select_nodes(conditions = startsWith(tooltip,suc)) %>% get_selection()
newg <- newg %>% clear_selection() %>%
select_edges(from = from_nodes, to = to_nodes) %>% delete_edges_ws
# newg %>% render_graph # debugging
} else {
newg <- graph
}
newg %>% generate_dot() %>% grViz(width = 1000, height = 2000) %>%
export_svg %>% svgPanZoom(height=800, controlIconsEnabled = TRUE)
})
}
shinyApp(ui=ui,server=server)
The (naive) solution simply revolves around iterating over selected values and filtering the graph accordingly.
library(shiny)
library(bupaR)
library(svgPanZoom)
library(DiagrammeRsvg)
library(DiagrammeR)
library(processmapR)
edges <- patients %>% process_map(performance(mean, "days"))
edges <- attr(edges, "edges")
colnames(edges)[1]<-"predecessor"
colnames(edges)[2]<-"successor"
graph <- process_map(patients
, type_nodes = frequency("absolute",color_scale = "Greys")
,type_edges = frequency("absolute",color_edges = "Greys"),
rankdir = "LR", render = FALSE)
ui <-shinyUI(fluidPage(
checkboxGroupInput("tran","Filter Transitions"
,choices = paste(edges$predecessor,"-",edges$successor)),
svgPanZoomOutput("pmap",height = 500,width = 1600)
))
server <- function(input, output) {
output$pmap <- renderSvgPanZoom({
if (all(!is.null(input$tran))){
#creating copy of graph for processing
ndf = get_node_df(graph)
edf = get_edge_df(graph)
newg = create_graph(nodes_df = ndf, edges_df = edf)
newg$global_attrs <- graph$global_attrs
for (t in input$tran){
pre <- strsplit(t, " - ")[[1]][[1]]
suc <- strsplit(t, " - ")[[1]][[2]]
#Finding edges to remove based on pre/suc nodes, selecting edge, removing
#using startWith due termination chars being added
from_nodes = newg %>% clear_selection() %>%
select_nodes(conditions = startsWith(tooltip,pre)) %>% get_selection()
to_nodes = newg %>% clear_selection() %>%
select_nodes(conditions = startsWith(tooltip,suc)) %>% get_selection()
newg <- newg %>% clear_selection() %>%
select_edges(from = from_nodes, to = to_nodes) %>% delete_edges_ws
# newg %>% render_graph # debugging
}
} else {
newg <- graph
}
newg %>% generate_dot() %>% grViz(width = 1000, height = 2000) %>%
export_svg %>% svgPanZoom(height=800, controlIconsEnabled = TRUE)
})
}
shinyApp(ui=ui,server=server)
Potential performance improvement would be to pre-calculate the edges selection, then the loop iteration would "just" take care of removing these.

shiny + leaflet map with and costum SHP lines - hoover over line to show data

I am trying to create shiny map, with custom shape lines. Each line has attached relevant data based on which colour is created. I would like to add for end user a possibility to see exact data for (label + speed variables from Spacial DataFrame) when they hoover over with mouse or click on line in the map
I thought leaflet would provide this functionality, but i can not do it properly, or I am using wrong library,
Code and example data:
library(sf)
library(sp)
library(tidyverse)
library(shiny)
library(leaflet)
lat <- 51 #center point latitude
long <- 17 #center point longitude
Street_Grid <- data.frame(
lat = c(lat - 1, lat + 1, lat - 1, lat + 1),
long = c(long - 1, long + 1, long + 1, long - 1),
label = c(1, 1, 2, 2),
data = rep(runif(2, 1, 10), each = 2)
) %>%
sf::st_as_sf(coords = c("lat", "long")) %>%
sf::st_set_crs(4326) %>%
group_by(label) %>%
summarize(speed = mean(data)) %>%
st_cast("LINESTRING")
## to 'SpatialLines'
sln <- as_Spatial(Street_Grid$geometry)
# to spacial lines data frame
ids <- data.frame()
for (i in (1:length(sln))) {
id <- data.frame(sln#lines[[i]]#ID)
ids <- rbind(ids, id)
}
colnames(ids)[1] <- "linkId"
Street_Grid_t <- cbind(ids, Street_Grid)
Street_Grid_tt <- data.frame(Street_Grid_t[,c(-1)])
row.names(Street_Grid_tt) <- Street_Grid_t$linkId
slndf <- SpatialLinesDataFrame(sl = sln, match.ID = T,
data = Street_Grid_tt)
# create interface
ui <- fluidPage(
theme = shinythemes::shinytheme("yeti"),
titlePanel(title = "SO Question"),
mainPanel("",
helpText("This is the polyline map"),
hr(),
leafletOutput("myMap", height = 400, width = 600))
)
# calculations
server <- function(input, output, session) {
pal <- colorNumeric(
palette = "inferno",
domain = slndf$data)
output$myMap <- renderLeaflet({
leaflet(slndf) %>%
addTiles() %>%
addPolylines(color = ~pal(speed ))
})
}
shinyApp(ui, server)

R shiny webapp - webapp runs & works locally but returns error when running app or deploying app to rshiny

This is my code. I am trying to create an interactive webapp where the user can add new data by drawing polygons and then the
I don't understand the error it returns when trying to click 'Run App' in RSTudio or deploy the app to shinyapps (as it works locally when I just run the code). Does someone have an idea why it doesn't work when running or deploying?
## install required packages (if not installed yet)
require(class)
require(dplyr)
require(leafem)
require(leaflet)
require(leaflet.extras)
require(raster)
require(rgdal)
require(rsconnect)
require(sf)
require(sp)
require(shiny)
#### 0. Import Data ####
## import landfills clusters spatial data (point data)
landfills_clusters <- readOGR("plasticleakagewebapp/data/landfill_clusters.gpkg")
landfills_clusters_sf <- st_read('plasticleakagewebapp/data/landfill_clusters.gpkg')
### import landfill polygons
landfills_polygons <- readOGR("plasticleakagewebapp/data/landfills/OpenLandfills_Vietnam.shp", use_iconv = T, encoding = "UTF-8")
## import shapefile of vietnam
vietnam <- readOGR("plasticleakagewebapp/data/vietnam/vietnam.shp")
#### 1. Interactive Map (Leaflet) ####
## plot map with landfills colored by cluster
# e.g. plot water distance < 500m in red
# create color palette
cof <- colorFactor(c("green","blue","red"), domain = c("1","2","3"))
map <- leaflet(landfills_clusters_sf) %>%
addProviderTiles(providers$Esri.WorldImagery) %>%
setView(lng = 105.48, lat = 15.54, zoom = 6) %>%
addMiniMap %>%
addPolygons(data = vietnam, fill = F, weight = 2, color = "#FFFFCC", group = "Outline") %>%
addPolygons(data = landfills_polygons, fill = F, weight = 2, color = "#FFFFCC") %>%
addCircleMarkers(data = landfills_clusters_sf, color = ~cof(km_cluster_unstand), radius = sqrt(landfills_clusters_sf$area_ha)*2,
fillOpacity = 0.5, label = ~name, group = "Risk") %>%
addLegend("bottomleft", colors= c("red","blue","green"), labels=c("high", "medium", "low"), title = "Leakage Risk")
map
#### Interactive Map ####
ui_inter <- fluidPage("Classification the Plastic Leakage Risk of Landfills in Vietnam", id = "nav",
tabPanel("Interactive Map",
div(class = "outer",
# If not using custom CSS, set height of leafletOutput to a number instead of percent
leafletOutput("map", width = "1700px", height = "800px"),
absolutePanel(id = "controls", class = "panel panel-default", fixed = T,
draggable = T, top = 60, left = "auto", right = 20, bottom = "auto",
width = 350, height = "auto",
h2("Plastic Leakage Risk"),
plotOutput("histRain", height = 200),
plotOutput("histWind", height = 200),
),
)
),
tabPanel("Data Explorer",
hr(),
# display the data in an interactive table
DT::dataTableOutput("landfills"),
textInput('Long', 'Enter new landfill longitude'),
textInput('Lat', 'Enter new landfill latitude'),
actionButton("update", "Update Table")
)
)
df <- landfills_clusters_sf[-c(2,9:10,12,16:17)] # select relevant columns
## add long & lat coordinates
df$long <- st_coordinates(landfills_clusters_sf)[,1]
df$lat <- st_coordinates(landfills_clusters_sf)[,2]
server_inter <- function(input, output, session) {
## create interactive map with leaflet
output$map <- renderLeaflet({
map %>%
# add toolbox to draw polygons
addDrawToolbar(
targetGroup = "drawnPoly",
rectangleOptions = F,
polylineOptions = F,
markerOptions = F,
editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions()),
circleOptions = F,
circleMarkerOptions = F,
polygonOptions = drawPolygonOptions(showArea = T, repeatMode = F, shapeOptions =
drawShapeOptions(fillColor = "orange", clickable = T))) %>%
addStyleEditor()
})
latlongs <- reactiveValues() # temporary to hold coords
latlongs$df2 <- data.frame(Longitude = numeric(0), Latitude = numeric(0))
## create empty reactive spdf to store drawn polygons
value <- reactiveValues()
value$drawnPoly <- SpatialPolygonsDataFrame(SpatialPolygons(list()), data = data.frame(notes=character(0), stringsAsFactors = F))
# fix the polygon to start another
observeEvent(input$map_draw_new_feature, {
coor <- unlist(input$map_draw_new_feature$geometry$coordinates)
Longitude <- coor[seq(1,length(coor), 2)]
Latitude <- coor[seq(2,length(coor), 2)]
isolate(latlongs$df2 <- rbind(latlongs$df2, cbind(Longitude, Latitude)))
poly <- Polygon(cbind(latlongs$df2$Longitude, latlongs$df2$Latitude))
polys <- Polygons(list(poly), ID = input$map_draw_new_feature$properties$`_leaflet_id`)
spPolys <- SpatialPolygons(list(polys))
print(spPolys)
value$drawnPoly <- rbind(value$drawnPoly, SpatialPolygonsDataFrame(spPolys, data = data.frame(notes = NA, row.names = row.names(spPolys))))
## add polygons to landfills polygons df
test <- SpatialPolygonsDataFrame(spPolys, data = data.frame(name = 1:length(spPolys), row.names = row.names(spPolys)))
test#data$area <- NA
test#data$Notes <- NA
test#data$location <- NA
test#proj4string <- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
new_polygons <- rbind(landfills_polygons, test)
## export new & old landfills to shapefile
shapefile(x = new_polygons, filename = "plasticleakagewebapp/data/landfill/OpenLandfills_Vietnam_total.shp", overwrite = T)
shapefile(x = test, filename = "plasticleakagewebapp/data/landfills/OpenLandfills_Vietnam_new.shp", overwrite = T)
## run DataPreparation script to calculate data of new landfill
source("plasticleakagewebapp/plasticleakage_datapreparation.R")
## import outcome of script
variables <- readOGR("plasticleakagewebapp/data/landfill_variables.gpkg")
# basic landfills as training data
train <- landfills_clusters#data[,c(7:8,11,13:15)]
# newly created landfills (from webapp) as testing data
test <- variables#data[,c(7:8,11,13:14)]
# predict risk class/cluster of new landfill (without re-running clustering algorithm)
knnClust <- class::knn(train = train[,-6], test = test, k = 1, cl = train$km_cluster_unstand)
knnClust
## add cluster as row
variables$km_cluster_unstand <- knnClust
# drop not needed columns
landfills_clusters$risk <- NULL
landfills_clusters$risk_label <- NULL
# combine all landfills into one spdf
new_variables <- rbind(landfills_clusters, variables)
## save results as shapefile
st_write(st_as_sf(new_variables), "plasticleakagewebapp/landfill_clusters_total.gpkg", overwrite = T, append = F)
## update plot upon ending draw
observeEvent(input$map_draw_stop, {
#replot it - take off the DrawToolbar to clear the features and add it back and use the values from the SPDF to plot the polygons
leafletProxy('map') %>%
removeDrawToolbar(clearFeatures = T) %>% removeShape('temp') %>% clearGroup('drawnPoly') %>%
addPolygons(data = value$drawnPoly, group = 'drawnPoly', color = "blue", layerId = row.names(value$drawnPoly)) %>%
addDrawToolbar(
targetGroup = "drawnPoly",
rectangleOptions = F,
polylineOptions = F,
markerOptions = F,
editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions()),
circleOptions = F,
polygonOptions=drawPolygonOptions(showArea = T, repeatMode = F, shapeOptions
= drawShapeOptions(fillColor = "orange", clickable = T)))
})
latlongs$df2 <- data.frame(Longitude = numeric(0), Latitude = numeric(0)) # clear df
## plot newly added landfills & risk cluster
leafletProxy("map", session) %>%
#addPolygons(data = landfills_polygons, fill = F, weight = 2, color = "#FFFFCC") %>%
addCircleMarkers(data = variables, color = ~cof(km_cluster_unstand), radius = sqrt(variables$area_ha)*2,
fillOpacity = 0.5, label = ~name, group = "Risk")
})
# create object for clicked marker (=landfill)
observeEvent(input$map_marker_click,{
## click returns clickid, long & lat
click <- input$map_marker_click
# if(is.null(click))
# return()
leafletProxy("map", session) %>% setView(lng = click$lng, lat = click$lat, zoom = 16)
})
# A reactive expression that returns the set of landfills that are in map bounds (to plot reactive graphs)
landfillsInBounds <- reactive({
if (is.null(input$map_bounds))
return(landfills_clusters_sf[FALSE,])
bounds <- input$map_bounds
latRng <- range(bounds$north, bounds$south)
lngRng <- range(bounds$east, bounds$west)
subset(landfills_clusters_sf,
st_coordinates(landfills_clusters_sf)[,2] >= latRng[1] & st_coordinates(landfills_clusters_sf)[,2] <= latRng[2] &
st_coordinates(landfills_clusters_sf)[,1] >= lngRng[1] & st_coordinates(landfills_clusters_sf)[,1] <= lngRng[2])
})
output$histRain <- renderPlot({
# If no zipcodes are in view, don't plot
if (nrow(landfillsInBounds()) == 0)
return(NULL)
hist(landfillsInBounds()$rain,
main = "Weather Data",
xlab = "Average Precipitation (mm)",
xlim = range(landfills_clusters_sf$rain),
col = '#00ffff',
border = 'white')
})
output$histWind <- renderPlot({
# If no landfills are in view, don't plot
if (nrow(landfillsInBounds()) == 0)
return(NULL)
hist(landfillsInBounds()$windspeed,
main = "",
xlab = "Average Wind Speed (km/h)",
xlim = range(landfills_clusters_sf$windspeed),
col = '#00DD00',
border = 'white')
})
output$landfills <- DT::renderDT({
df
})
}
# Run the app
shinyApp(ui_inter, server_inter)
Error which is returend when clicking "Run App" in RStudio:
Fehler in ogrListLayers(dsn = dsn) : Cannot open data source

Performance problems in R's Shiny on huge (?) dataset

I have a dataset of ~10.000 address pairs (origin, destination) which consists of two sources - a database and a CSV-file. I am visualizing those pairs of addresses by two different marker types and I visualize the connections between those pairs with a line. It's possible to toggle the visibility of origins, destinations, and connections. It's also possible to draw a polygon on the map to frame markers and then visualize the corresponding markers and connections (you can choose if the polygon should frame origins, destinations or both). And it's possible to toggle the datasource (CSV or database) and choose data by date.
All of this works quite well, I just wanted to make clear where and that I need to use reactive values. But the performance is way to slow. It takes a lot of time to load this application when running it with RStudio and it could not be loaded on Shiny Server because the connection breaks down. I'm don't use the Pro version of Shiny Server where the timeout is not settable out of the box.
I tried to speed up the application by using the leafletProxy as often as possible.
df.data.db <- getDataFromDb() #external function
df.data.csv <- getDataFromCsv() #external function
df.data.total <- rbind(df.data.db,df.data.csv)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
tags$head(tags$style(HTML('.dest {color: rgba(11, 221, 25, 0.7);}'))),
tags$head(tags$style(HTML('.orig {color: rgba(255,100,20);}'))),
leafletOutput("map", height = "85%"),
fluidRow(
column(
3,
p(tags$b("Datasets")),
materialSwitch(inputId = "useDatabase", label = "database",value=TRUE),
materialSwitch(inputId = "useExcel", label = "excel",value=TRUE)),
column(
3,
p(),
dateRangeInput('dateRange',
label = 'Date range input: yyyy-mm-dd',
start = "2016-12-26",
end = Sys.Date(),
min = "2016-12-26",
max = Sys.Date()),
p(),
textOutput("number_of_data")
),
column(3,
p(),
actionButton("remove", "Remove shapes")),
column(3,
p(tags$b("Connections")),
textOutput("number_of_connections"))
)
)
server <- function(input, output, session) {
reactiveData <- reactiveValues(
markers = data.frame(lat = numeric(), lon = numeric()),
allPoly = data.frame(lat = numeric(), lon = numeric()),#should polygon frame all markers
origPoly = data.frame(lat = numeric(), lon = numeric()),#only origin markers
destPoly = data.frame(lat = numeric(), lon = numeric()),#only destination markers
shapeState = "poly_all",#what polygon type is drawn
connections=0
)
#used subset of data depending of the chosen date
mydata <- reactive({
base = base_data()
from <- input$dateRange[1]
to <- input$dateRange[2]
return(base[base$date>=from & base$date<=to,])
})
#choose data source (csv or db)
base_data <- reactive({
mydf = data.frame(orig_lat=numeric(),
orig_lon=numeric(),
dest_lat=numeric(),
dest_lon=numeric(),
date=as.Date(character()))
if(input$useExcel==TRUE && input$useDatabase==TRUE)
mydf = df.data.total
else if(input$useExcel==FALSE && input$useDatabase==TRUE)
mydf = df.data.db
else if(input$useExcel==TRUE && input$useDatabase==FALSE)
mydf = df.data.csv
reactiveData$connections <- nrow(mydf)
return(mydf)
})
#show / hide connections
observe({
leafletProxy("map",session = session) %>%
clearShapes() %>%
clearGroup("Connections")
conn.data <- mydata();
for(i in 1:nrow(conn.data)) {
row <- conn.data[i,]
leafletProxy("map",session = session) %>% addPolygons(lat=c(row$orig_lat,row$dest_lat),lng=c(row$orig_lon,row$dest_lon),group="Connections",weight=0.5)
}
})
#remove all customized stuff
observeEvent(input$remove,{
reactiveData$markers <- data.frame(lat = numeric(), lon = numeric())
reactiveData$allPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$origPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$destPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$shapeState <- "poly_all"
reactiveData$connections<-0
leafletProxy("map",session = session) %>%
clearShapes() %>%
clearGroup("polygon") %>%
clearGroup("polymarkers")%>%
clearGroup("polyconnections") %>%
showGroup("Origins") %>%
showGroup("Destinations") %>%
clearGroup("tempmarkers")
})
#my map
output$map <- renderLeaflet({
leaflet(data=mydata()) %>%
addTiles()%>%
setView("7.126501","48.609749", 10) %>%
addMarkers(
lng=~dest_lon,
lat=~dest_lat,
icon = uix.destMarker,
group = "Destinations",
layerId = "dest_layer",
clusterId = "dest_cluster",
clusterOptions = markerClusterOptions(
removeOutsideVisibleBounds = TRUE,
iconCreateFunction=js.destclusters
)) %>%
addMarkers(
lng=~orig_lon,
lat=~orig_lat,
icon = uix.origMarker,
group = "Origins",
layerId = "orig_layer",
clusterId = "orig_cluster",
clusterOptions = markerClusterOptions(
removeOutsideVisibleBounds = TRUE,
iconCreateFunction=js.origclusters
)) %>%
addLayersControl(overlayGroups = c("Origins","Destinations","Connections"))
})
#print markers for polygon on map
observeEvent(input$map_click,{
leafletProxy("map",session = session) %>%
hideGroup("Connections")
if(nrow(reactiveData$allPoly)>0){
reactiveData$markers <- data.frame(lat = numeric(), lon = numeric())
reactiveData$allPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$origPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$destPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$shapeState <- "poly_all"
reactiveData$connections<-0
leafletProxy("map",session = session) %>%
clearShapes() %>%
clearGroup("polygon") %>%
clearGroup("polymarkers")%>%
clearGroup("polyconnections") %>%
showGroup("Origins") %>%
showGroup("Destinations") %>%
clearGroup("tempmarkers")
}
if(nrow(reactiveData$origPoly)>0 && nrow(reactiveData$destPoly)>0){
showModal(modalDialog(
title = "Wrong workflow",
"Remove old shapes first!",
easyClose = TRUE
))
}
else{
click <- input$map_click
clat <- click$lat
clng <- click$lng
reactiveData$markers[nrow(reactiveData$markers) + 1, ] = c(clat, clng)
leafletProxy('map') %>%
addMarkers(lng = reactiveData$markers$lon,
lat = reactiveData$markers$lat,
group="polymarkers"
)
}
})
#change type of polygon by clicking on polygon. hiding connections by clicking on it
observeEvent(input$map_shape_click,{
click <- input$map_shape_click
if(click$group=="Connections"){
leafletProxy("map",session = session) %>%
hideGroup("Connections")
clat <- click$lat
clng <- click$lng
leafletProxy('map') %>%
addMarkers(lng = clng,
lat = clat)
reactiveData$markers[nrow(reactiveData$markers) + 1, ] = c(clat, clng)
}
else if(click$group =="polygon" && nrow(reactiveData$markers)==0){
tmp <- data.frame(lat = numeric(), lon = numeric())
if(reactiveData$shapeState=="poly_all") {
reactiveData$shapeState<-"poly_orig"
isolate(tmp<-reactiveData$allPoly)
reactiveData$origPoly <- rbind(reactiveData$origPoly,tmp)
reactiveData$allPoly<- data.frame(lat = numeric(), lon = numeric())
#reactiveData$destPoly <- rbind(reactiveData$destPoly,data.frame(lat = numeric(), lon = numeric()))
}
else if(reactiveData$shapeState=="poly_orig") {
reactiveData$shapeState<-"poly_dest"
isolate(tmp<-reactiveData$origPoly)
reactiveData$origPoly <- data.frame(lat = numeric(), lon = numeric())
#reactiveData$allPoly <- data.frame(lat = numeric(), lon = numeric())
reactiveData$destPoly <- rbind(reactiveData$destPoly,tmp)
}
else if(reactiveData$shapeState=="poly_dest") {
reactiveData$shapeState<-"poly_all"
isolate(tmp<-reactiveData$destPoly)
#reactiveData$origPoly <- rbind(reactiveData$origPoly,data.frame(lat = numeric(), lon = numeric()))
reactiveData$allPoly <- rbind(reactiveData$allPoly,tmp)
reactiveData$destPoly <- data.frame(lat = numeric(), lon = numeric())
}
createConnections()
leafletProxy('map') %>% # use the proxy to save computation
clearGroup("polygon") %>%
addPolygons(lat = tmp$lat, lng = tmp$lon, group="polygon",color = polyColor(),fillColor=polyColor())
}
else if(nrow(reactiveData$markers)>0){
showModal(modalDialog(
title = "Wrong workflow",
"It's too late to change the type of your selection. Please clear shapes and draw again!",
easyClose = TRUE
))
}
})
polyColor <- reactive({
if(reactiveData$shapeState=="poly_all") {
return("black")
}
else if(reactiveData$shapeState=="poly_orig") {
return("red")
}
else if(reactiveData$shapeState=="poly_dest") {
return("green")
}
})
createConnections <- reactive({
reactiveData$connections<-0
df.pois <- data.frame(lat=numeric(),lon=numeric())
data <- mydata()
allData <- data.frame(orig_lat=numeric(),
orig_lon=numeric(),
dest_lat=numeric(),
dest_lon=numeric(),
date=as.Date(character()))
if(nrow(reactiveData$allPoly)>0){
df.pois<-rbind(data.frame(lat=data$orig_lat, lon=data$orig_lon),
data.frame(lat=data$dest_lat, lon=data$dest_lon))
my_poly <- reactiveData$allPoly
pois <- SpatialPoints(df.pois)
poiPoly <- SpatialPolygons(list(Polygons(list(
Polygon(cbind(my_poly$lat, my_poly$lon))
), ID = "x11")))
coords<-as.data.frame(pois[poiPoly])
if(nrow(coords)>0){
allData1<-subset(data,((data$orig_lat %in% coords$lat)))
allData1<-subset(allData1,((allData1$orig_lon %in% coords$lon)))
allData2<-subset(data,((data$dest_lat %in% coords$lat)))
allData2<-subset(allData2,((allData2$dest_lon %in% coords$lon)))
allData<-rbind(allData1,allData2)
}
}else {
if(nrow(reactiveData$origPoly)>0){
df.pois<-data.frame(lat=data$orig_lat, lon=data$orig_lon)
my_poly <- reactiveData$origPoly
pois <- SpatialPoints(df.pois)
poiPoly <- SpatialPolygons(list(Polygons(list(
Polygon(cbind(my_poly$lat, my_poly$lon))
), ID = "x11")))
coords<-as.data.frame(pois[poiPoly])
allData1<-subset(data,((data$orig_lat %in% coords$lat)))
allData1<-subset(allData1,((allData1$orig_lon %in% coords$lon)))
allData<-allData1
data<-allData
}
if(nrow(reactiveData$destPoly)>0){
df.pois<-data.frame(lat=data$dest_lat, lon=data$dest_lon)
my_poly <- reactiveData$destPoly
pois <- SpatialPoints(df.pois)
poiPoly <- SpatialPolygons(list(Polygons(list(
Polygon(cbind(my_poly$lat, my_poly$lon))
), ID = "x11")))
coords<-as.data.frame(pois[poiPoly])
total <- mydata()
allData2<-subset(data,((data$dest_lat %in% coords$lat)))
allData2<-subset(allData2,((allData2$dest_lon %in% coords$lon)))
allData<-allData2
}
}
leafletProxy("map",session = session) %>%
clearGroup("polyconnections")
leafletProxy("map",session = session) %>%
hideGroup("Origins") %>%
hideGroup("Destinations") %>%
clearGroup("tempmarkers")
if(nrow(allData)>0){
reactiveData$connections<-nrow(allData)
leafletProxy("map",session = session,data=allData) %>%
addMarkers(
lng=~dest_lon,
lat=~dest_lat,
icon = uix.destMarker,
group = "tempmarkers"
) %>%
addMarkers(
lng=~orig_lon,
lat=~orig_lat,
icon = uix.origMarker,
group = "tempmarkers"
)
for(i in 1:nrow(allData)) {
row <- allData[i,]
leafletProxy("map",session = session) %>%
addPolygons(lat=c(row$orig_lat,row$dest_lat),lng=c(row$orig_lon,row$dest_lon),group="polyconnections",weight=1)
}
}
})
observeEvent(input$map_marker_click, {
my_poly <- data.frame(lat=numeric(),lon=numeric())
if (nrow(reactiveData$markers) >= 4) {
my_poly <- rbind(my_poly,reactiveData$markers)
if(reactiveData$shapeState=="poly_all") {
reactiveData$allPoly <- rbind(reactiveData$allPoly,my_poly)
}
else if(reactiveData$shapeState=="poly_orig") {
reactiveData$destPoly <- rbind(reactiveData$destPoly,my_poly)
reactiveData$shapeState = "poly_dest"
}
else if(reactiveData$shapeState=="poly_dest") {
reactiveData$origPoly <- rbind(reactiveData$origPoly,my_poly)
reactiveData$shapeState = "poly_orig"
}
leafletProxy('map') %>% # use the proxy to save computation
addPolygons(lat = my_poly$lat, lng = my_poly$lon, group="polygon",color = polyColor(),fillColor=polyColor())
createConnections()
reactiveData$markers <- data.frame(lat=numeric(),lon=numeric())
}
})
}
shinyApp(ui, server)
I don't think that a dataset of 10.000 pairs is "large" for statistics and I'm pretty sure R is designed well enough to handle this amount of data, so I guess it's leaflet itself or my faulty usage of leaflet or reactive data.
I'm also not very sure about the creation of the lines between origins and destinations which also takes a lot of time but I could not find an easier method to draw a simple line between two points on leaflet.
for(i in 1:nrow(conn.data)) {
row <- conn.data[i,]
leafletProxy("map",session = session) %>% addPolygons(lat=c(row$orig_lat,row$dest_lat),lng=c(row$orig_lon,row$dest_lon),group="Connections",weight=0.5)
}

Resources