I want to draw several things on a leaflet map (through Shiny/R)
I initialize the map like this
map = leaflet() %>% addProviderTiles("Stamen.TonerLite") %>% setView(-1.5, 53.4, 9)
output$myMap = renderLeaflet(map)
Then, depending on what is clicked in the App I ether want to draw Markers or a Polygon
sp <- reactiveValues()
ep <- reactiveValues()
area <- reactiveValues()
area$mp <- matrix(...) # empty matrix with 2 cols named lat/lng
observeEvent(input$map_click, {
coords <- input$map_click
if ( (!is.null(as.integer(input$button)) && (!is.null(coords))) ) {
if (as.integer(input$button) == 1) {
sp[["lat"]] <- coords$lat
sp[["lng"]] <- coords$lng
} else if (as.integer(input$button) == 2) {
ep[["lat"]] <- coords$lat
ep[["lng"]] <- coords$lng
} else if (as.integer(input$button) == 3) {
cm <- matrix(data = c(coords$lat, coords$lng), nrow = 1, ncol = 2)
area$mp <- rbind(area$mp, cm)
} else {
print("Kawum!")
}
})
What I cannot get into my head is how to draw something now on the leaflet map.
What is group ID, what is layer ID. Where comes leafletProxy into play?
How would I, depending on which if else statement kicks in, send the data to leaflet and add a marker or a polygon?
Any help or pointing into the right direction is highly appreciated!
Maybe this can clarify things:
library(shiny)
library(leaflet)
ui <- shinyUI(fluidPage(
actionButton("button", "Change style!"),
leafletOutput("myMap")
))
server <- function(input, output){
map = leaflet() %>% addProviderTiles("Stamen.TonerLite") %>% setView(-1.5, 53.4, 9)
output$myMap = renderLeaflet(map)
sp <- reactiveValues()
ep <- reactiveValues()
area <- reactiveValues()
observeEvent(sp$lat, {
leafletProxy("myMap") %>% addMarkers(lat = sp$lat, lng = sp$lng)
})
observeEvent(ep$lat, {
leafletProxy("myMap") %>% addCircles(lat = ep$lat, lng = ep$lng)
})
observeEvent(area$mp, {
leafletProxy("myMap") %>% addPolygons(lat = area$mp[ , 1], lng = area$mp[ , 2])
})
observeEvent(input$myMap_click, {
coords <- input$myMap_click
if ( (!is.null(input$button) && (!is.null(coords))) ) {
if (input$button %% 4 == 1) {
sp[["lat"]] <- coords$lat
sp[["lng"]] <- coords$lng
} else if (input$button %% 4 == 2) {
ep[["lat"]] <- coords$lat
ep[["lng"]] <- coords$lng
} else if (input$button %% 4 == 3) {
cm <- matrix(data = c(coords$lat, coords$lng), nrow = 1, ncol = 2)
area$mp <- if(!is.null(area$mp)){rbind(area$mp, cm)}else{cm}
} else {
print("Kawum!")
}
}
})
}
shinyApp(ui, server)
First thing, the click event needs to be named after the output element. So input$myMap_click gives you the coords. Second, the leaflet proxy is designed to draw points, things etc. into existing maps. Imagine you'd always re-render the map to do leaflet() %>% addMarkers(...). leafletProxy just needs the output element's name and draws the markers on top of it.
The code above shows some things you can do with that. E.g. using the polygons.
Try using it and comment, if there is something unclear.
Related
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(). What I acually need is to hide/display the edges between the nodes if deselect/select one transition pair
All the transitions can be seen from the obect edges which I extract from the process_map() object at the beginning.
library(shiny)
library(bupaR)
library(svgPanZoom)
library(DiagrammeRsvg)
library(processmapR)
library(DiagrammeR)
f <- \(data, nofrom, noto) {
u <- attr(data, 'edges')
`attr<-`(data, 'edges', u[u$from != nofrom & u$to != noto,,drop=FALSE])
}
edges<-patients %>%
process_map(performance(mean, "days"))
edges <- attr(edges, "edges")
colnames(edges)[1]<-"predecessor"
colnames(edges)[2]<-"successor"
ui <-shinyUI(fluidPage(
selectInput("tran","transitions",choices = paste(edges$predecessor,"-",edges$successor),
selected = paste(edges$predecessor,"-",edges$successor),multiple = T),
svgPanZoomOutput("pmap",height = 500,width = 1600)
))
server <- function(input, output) {
output$pmap <- renderSvgPanZoom({
req(input$tran)
pre <- strsplit(input$tran, " - ")[[1]][[1]]
suc <- strsplit(input$tran, " - ")[[1]][[2]]
p<-process_map(patients, type_nodes = frequency("absolute",color_scale = "Greys")
,type_edges = frequency("absolute",color_edges = "Greys"),
rankdir = "LR", render = FALSE)
p1<-f(data=p, nofrom=pre, noto=suc)
p1%>% generate_dot() %>%
grViz(width = 1000, height = 2000) %>% export_svg %>%
svgPanZoom(height=800, controlIconsEnabled = TRUE)
})
}
shinyApp(ui=ui,server=server)
I have some large shapefiles that I want to run some intersect analysis on using user input on a leaflet map. My shiny page displays a map that a user can draw a polygon on and I want to see if that polygon intersects either of the two shapefiles. I built a working version of this where the server reads in the shapefiles each time a user connects to the server, but obviously that isn't a great user experience. So I have been trying to move the shapefile reading to global variables that the server loads once, and the user just has to run the intersect on. I'll skip posting my 3 lines of UI for now but this app works locally, it's only when I run it on a dedicated Shiny server that is crashes after the user "closes" the polygon. I have a feeling it's an issue with the global variable declarations, but there is no log file generated so I'm having a really hard time debugging it.
App.R
library(shiny)
source("/ui.R")
source("/server.R")
shpfile1 <- st_read("path_to_shpfile1")
shpfile2 <- st_read("path_to_shpfile2")
ui <- ui()
server <- server()
shinyApp(ui = ui, server = server)
Server.R
# a number of libraries
server <- function(input, output, session) {
output$s1 <- renderText({"Define project area..."})
output$s2 <- renderText({"Define project area..."})
print("Reading New Jersey boundary...")
mapStates = map("state", "New Jersey", fill = FALSE, plot = FALSE)
output$map <- renderLeaflet({
leaflet(data = mapStates) %>% addTiles() %>% addPolygons(fillColor = topo.colors(10, alpha=0.8), stroke=FALSE) %>%
addDrawToolbar(targetGroup = "projectArea",
rectangleOptions = F,
polylineOptions = F,
markerOptions = F,
circleMarkerOptions = F,
editOptions = editToolbarOptions(edit = FALSE, remove = TRUE, selectedPathOptions = selectedPathOptions()),
circleOptions = F)
})
observeEvent(input$drawPoints, {
proxy %>% clearShapes()
for (i in seq_along(data)) {
proxy %>% addPolygons(
data[[i]][,"lon"],
data[[i]][,"lat"],
layerId=i,
opacity=0.4,
color = c('red','green')[i]
)
Sys.sleep(2) # - this is to see first (red) polygon
}
})
observeEvent(input$map_draw_new_feature, {
withProgress(message = "Please wait...", value = 0, {
# capture project area and convert to usable format for intersecting
feat <- input$map_draw_new_feature
coords <- unlist(feat$geometry$coordinates)
coords <- matrix(coords, ncol=2, byrow=TRUE)
poly <- st_sf(st_sfc(st_polygon(list(coords))), crs = "+proj=longlat +datum=WGS84")
# intersect project area with constraint layers
incProgress(1/3, detail = "Analyzing shapefile1...")
i_shp1 <- st_intersects(poly, shpfile1)
c_shp1 <- sapply(i_shp1, length)
incProgress(2/3, detail = "Analyzing shapefile2...")
i_shp2 <- st_intersects(poly, shpfile2)
c_shp2 <- sapply(i_shp2, length)
if(c_streams > 0) {
output$s1 <- renderText({"does intersect shapefile 1"})
} else {
output$s1 <- renderText({"does not intersect shapefile 1"})
}
if(c_wetlands > 0) {
output$s2 <- renderText({"does intersect shapefile 2"})
} else {
output$s2 <- renderText({"does not intersect shapefile 2"})
}
})
})
}
I have a bunch of reactive filters based on user inputs. I want to addCircleMarkers on my leaflet map only if the filters do not return a NULL value. How can I conditionally addCircleMarkers to a leaflet map in Shiny? Right now it seems that it only plots the results from the second filter instead of both even if user inputs are not NULL for both. My guess is that the second addCircleMarkers function is overwriting the first instead of adding more circles to the map. Here's my server code below:
server.R
server <- function(input, output) {
relig_pal <- colorFactor("magma", unique(all_cleaned$religion))
denom_pal <- colorFactor("viridis", unique(all_cleaned$denom))
output$mymap <- renderLeaflet({
input$years_map
input$map_button
isolate({
map <- leaflet() %>% addTiles()
if(!is.null(geography_1())) {
marker_1 <- addCircleMarkers(map = map, data = geography_1(),
radius = ~ifelse(is.na(denom), log(religion_population),
log(denom_pop)),
color = ~ifelse(is.na(denom), relig_pal(religion),
denom_pal(denom)),
label = ~ifelse(is.na(denom),
paste("Religion:",religion,
"Population:",religion_population),
paste("Denomination:", denom,
"Population:", denom_pop))
)
}
if(!is.null(geography_2())) {
marker_1 %>% addCircleMarkers(data = geography_2(),
radius = ~ifelse(is.na(denom), log(religion_population),
log(denom_pop)),
color = ~ifelse(is.na(denom), relig_pal(religion),
denom_pal(denom)),
label = ~ifelse(is.na(denom),
paste("Religion:", religion,
"Population:", religion_population),
paste("Denomination:", denom,
"Population:", denom_pop))
)
}
})
})
year <- reactive({
req(input$years_map)
all_cleaned %>% filter(year == input$years_map)
})
religion_1 <- reactive({
req(input$religion_1_map)
if(input$religion_1_map == "All") {
year()
}
else if(input$religion_1_map == "None") {
return()
}
else {
year() %>% filter(religion == input$religion_1_map)
}
})
denom_1 <- reactive({
req(input$denom_1_map)
if(input$denom_1_map == "All") {
religion_1()
}
else if(input$denom_1_map == "None") {
religion_1() %>% filter(is.na(denom))
}
else {
religion_1() %>% filter(denom == input$denom_1_map)
}
})
geography_1 <- reactive({
req(input$geography_1_map)
if(input$geography_1_map == "All") {
denom_1()
}
else if(input$geography_1_map == "None") {
return()
}
else {
denom_1() %>% filter(country_name == input$geography_1_map)
}
})
religion_2 <- reactive({
req(input$religion_2_map)
if(input$religion_2_map == "All") {
year()
}
else if(input$religion_2_map == "None") {
return()
}
else {
year() %>% filter(religion == input$religion_2_map)
}
})
denom_2 <- reactive({
req(input$denom_2_map)
if(input$denom_2_map == "All") {
religion_2()
}
else if(input$denom_2_map == "None") {
religion_2() %>% filter(is.na(denom))
}
else {
religion_2() %>% filter(denom == input$denom_2_map)
}
})
geography_2 <- reactive({
req(input$geography_2_map)
if(input$geography_2_map == "All") {
denom_2()
}
else if(input$geography_2_map == "None") {
return()
}
else {
denom_2() %>% filter(country_name == input$geography_2_map)
}
})
}
Error message:
Error:no applicable method for 'filter_' applied to an object of class "NULL"
Thanks in advance for any help!
Once you draw markers on the map, you need to save those to a variable (e.g. see how we solved this problem here.
For the second question (you should really post them separately), try paste instead of cat, e.g. paste(statement1, statement2, sep = "\n").
Update:
I have managed to solve the problem using #Roman Luštrik 's suggestion of storing the first circle marker as a variable and using a placeholder to plot a point of opacity 0 instead of dealing with NULL values whenever nothing is supposed to appear on the plot, which I couldn't quite figure out.
I wanna build quadrants on my leaflet as part of my quadrat analysis. currently I have my tessalation object and im trying to draw the tiles on my leaflet. My code is below
library(spatstat)
library(leaflet)
firms_ppp <- ppp(x=cbd_points#coords[,1],y=cbd_points#coords[,2], window =
window)
qc <- quadratcount(firms_ppp)
qc.nu <- as.numeric(qc)
qc.tess <- as.tess(qc)
colorpal4 <- colorNumeric("red",c(min(qc.nu, na.rm = TRUE),max(qc.nu, na.rm = TRUE)))
for (j in 1:length(qc.tess$window$yrange)) {
for (i in 1:length(qc.tess$window$xrange[i])) {
leaflet() %>%
addRectangles(lng1 = qc.tess$window$xrange[i], lng2 = qc.tess$window$xrange[i+1],
lat1 = rev(qc.tess$window$yrange)[j], lat2 = rev(qc.tess$window$yrange)[j+1],
color = colorpal4(qc.nu[j+(i-1)*(length(qc.tess$window$yrange)-1)]),
popup = paste("<h3>",qc.nu[j+(i-1)*(length(qc.tess$window$yrange)-1)],"</h3>")
)
}
}
Any idea how I can build the quadrants? I tried with tiles as well but I cant seem to get it to work too! Pls Help!!
With 2 helping functions found here, which convert a Tesselation object into SpatialPolygons, you can achieve something like this:
library(spatstat)
library(leaflet)
library(sp)
## FUNCTIONS #####################
owin2Polygons <- function(x, id="1") {
stopifnot(is.owin(x))
x <- as.polygonal(x)
closering <- function(df) { df[c(seq(nrow(df)), 1), ] }
pieces <- lapply(x$bdry,
function(p) {
Polygon(coords=closering(cbind(p$x,p$y)),
hole=spatstat.utils::is.hole.xypolygon(p)) })
z <- Polygons(pieces, id)
return(z)
}
tess2SP <- function(x) {
stopifnot(is.tess(x))
y <- tiles(x)
nom <- names(y)
z <- list()
for(i in seq(y))
z[[i]] <- owin2Polygons(y[[i]], nom[i])
return(SpatialPolygons(z))
}
## DATA #####################
cbd_points <- data.frame(
long = runif(100,15,19),
lat = runif(100,40,50)
)
window <- owin(c(0,20), c(30,50))
firms_ppp <- ppp(x=cbd_points$long, y=cbd_points$lat, window = window)
qc <- quadratcount(firms_ppp)
qc.nu <- as.numeric(qc)
qc.tess <- as.tess(qc)
colorpal4 <- colorNumeric("red",c(min(qc.nu, na.rm = TRUE),max(qc.nu, na.rm = TRUE)))
PolyGrid <- tess2SP(qc.tess)
PolyGridDF <- SpatialPolygonsDataFrame(PolyGrid, data = data.frame(ID = 1:length(PolyGrid)), match.ID = F)
## SHINY ########################
library(shiny)
ui <- fluidPage(
leafletOutput("map")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
pal = colorFactor("viridis", as.character(PolyGridDF$ID))
leaflet() %>%
addTiles() %>%
addPolygons(data=PolyGridDF,
label = as.character(PolyGridDF$ID),
color = ~pal(as.character(PolyGridDF$ID)))
})
}
shinyApp(ui, server)
I created an application in R-shiny, using the leaflet.extra package, I put a map in which my users can draw polygons, my goal is to be able to download the polygons that my users drew as a GeoJson or Shapefil (.shp) .
My application looks like this:
ui <- fluidPage(
textOutput("text"),leafletOutput("mymap") )
and server:
poly<-reactiveValues(poligonos=list()) #save reactiveValues
output$mymap <- renderLeaflet({
leaflet("mymap") %>%
addProviderTiles(providers$Stamen.TonerLite, #map type or map theme. -default($Stame.TonerLite)
options = providerTileOptions(noWrap = TRUE)
)%>% addDrawToolbar(
targetGroup='draw',
editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions())) %>%
addLayersControl(overlayGroups = c('draw'), options =
layersControlOptions(collapsed=FALSE)) %>%
addStyleEditor()
})
polygons<- eventReactive(input$mymap_draw_all_features, {
features<-input$mymap_draw_all_features
poly$poligonos<-c(poly$poligonos,features)
return(poly$poligonos)
})
The eventReactive function called "polygons" is responsible for recording the polygons (coordinates) that are drawn, but i din't know how to save them or convert into a GeoJson or shapefile format.
What you can do is take the coordinates of the polygons you made with the DrawToolbar and use them to create polygons in a reactiveValues SpatialPolygonsDataFrame. You can export that SPDF as a shapefile (with the example below you have to publish to the server to make the download option work. It will not work from R Studio).
ui <- fluidPage(
textOutput("text"),leafletOutput("mymap"),
downloadButton('downloadData', 'Download Shp'))
--
server<- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet("mymap") %>%
addProviderTiles(providers$Stamen.TonerLite, #map type or map theme. -default($Stame.TonerLite)
options = providerTileOptions(noWrap = TRUE)) %>%
addDrawToolbar(targetGroup = "drawnPoly",
rectangleOptions = F,
polylineOptions = F,
markerOptions = F,
editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions()),
circleOptions=F,
polygonOptions=drawPolygonOptions(showArea=TRUE, repeatMode=F , shapeOptions=drawShapeOptions( fillColor="red",clickable = TRUE))) %>%
addStyleEditor()
})
latlongs<-reactiveValues() #temporary to hold coords
latlongs$df2 <- data.frame(Longitude = numeric(0), Latitude = numeric(0))
#########
#empty reactive spdf
value<-reactiveValues()
SpatialPolygonsDataFrame(SpatialPolygons(list()), data=data.frame (notes=character(0), stringsAsFactors = F))->value$drawnPoly
#fix the polygon to start another
observeEvent(input$mymap_draw_new_feature, {
coor<-unlist(input$mymap_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$mymap_draw_new_feature$properties$`_leaflet_id`)
spPolys<-SpatialPolygons(list(polys))
#
value$drawnPoly<-rbind(value$drawnPoly,SpatialPolygonsDataFrame(spPolys,
data=data.frame(notes=NA, row.names=
row.names(spPolys))))
###plot upon ending draw
observeEvent(input$mymap_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('mymap') %>% removeDrawToolbar(clearFeatures=TRUE) %>% removeShape('temp') %>% clearGroup('drawnPoly') %>% addPolygons(data=value$drawnPoly, popup="poly", 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=TRUE, repeatMode=F , shapeOptions=drawShapeOptions( fillColor="red",clickable = TRUE)))
})
latlongs$df2 <- data.frame(Longitude = numeric(0), Latitude = numeric(0)) #clear df
})
########################
### edit polygons / delete polygons
observeEvent(input$mymap_draw_edited_features, {
f <- input$mymap_draw_edited_features
coordy<-lapply(f$features, function(x){unlist(x$geometry$coordinates)})
Longitudes<-lapply(coordy, function(coor) {coor[seq(1,length(coor), 2)] })
Latitudes<-lapply(coordy, function(coor) { coor[seq(2,length(coor), 2)] })
polys<-list()
for (i in 1:length(Longitudes)){polys[[i]]<- Polygons(
list(Polygon(cbind(Longitudes[[i]], Latitudes[[i]]))), ID=f$features[[i]]$properties$layerId
)}
spPolys<-SpatialPolygons(polys)
SPDF<-SpatialPolygonsDataFrame(spPolys,
data=data.frame(notes=value$drawnPoly$notes[row.names(value$drawnPoly) %in% row.names(spPolys)], row.names=row.names(spPolys)))
value$drawnPoly<-value$drawnPoly[!row.names(value$drawnPoly) %in% row.names(SPDF),]
value$drawnPoly<-rbind(value$drawnPoly, SPDF)
})
observeEvent(input$mymap_draw_deleted_features, {
f <- input$mymap_draw_deleted_features
ids<-lapply(f$features, function(x){unlist(x$properties$layerId)})
value$drawnPoly<-value$drawnPoly[!row.names(value$drawnPoly) %in% ids ,]
})
#write the polys to .shp
output$downloadData<-downloadHandler(
filename = 'shpExport.zip',
content = function(file) {
if (length(Sys.glob("shpExport.*"))>0){
file.remove(Sys.glob("shpExport.*"))
}
proj4string(value$drawnPoly)<-"+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
writeOGR(value$drawnPoly, dsn="shpExport.shp", layer="shpExport", driver="ESRI Shapefile")
zip(zipfile='shpExport.zip', files=Sys.glob("shpExport.*"))
file.copy("shpExport.zip", file)
if (length(Sys.glob("shpExport.*"))>0){
file.remove(Sys.glob("shpExport.*"))
}
}
)
}
--
shinyApp(ui=ui,server=server)