Slav Slav - 2 months ago 33
R Question

R Reactive values - unique added to list

Reactive values might be a curse to someone from different programming language background;)
The task ahead (simplified for SO) - I want to download new file whenever new country is within the map boundaries. In the example below you would need a shapefile, for example this one: http://thematicmapping.org/downloads/TM_WORLD_BORDERS_SIMPL-0.3.zip
UI is as simple as it can be:

library(leaflet)
library(shiny)
fluidPage(leafletOutput("mymap"))


The server is not overly complicated (yet;)

library(shiny)
library(DT)
library(leaflet)
library(rgdal)
library(maptools)
library(rgeos)

countries<- readShapeSpatial("TM_WORLD_BORDERS_SIMPL-0.3.shp")

displayed<-c("United Kingdom")

server <- function(input, output,session) {

output$mymap <- renderLeaflet({
leaflet(countries) %>%
addTiles() %>%
setView(lng=-0.1294984,lat=51.4992921,zoom=10)

})

pointsInBounds <- reactive({
if (is.null(input$mymap_bounds))
return(NULL)
bounds <- input$mymap_bounds
N<- bounds$north
S<-bounds$south
E<-bounds$east
W<-bounds$west
BB = matrix(c(W,E,E,W,W,N,N,S,S,N), nrow=5,ncol=2)
BB = SpatialPolygons(list(Polygons(list(Polygon(BB)),1)))
as.vector(countries[which(gIntersects(countries,BB,byid=TRUE)),]$NAME)
})}


So the
pointsinbounds()
tells me what countries are visible,
displayed
is a list of what is already downloaded (to avoid re-downloading).
What i want to achieve is to add (and keep)
pointsinbounds()
to
displayed
list and observe the
displayed
list if it changes (to trigger downloads only if a new country is within the boundaries). I managed adding to the list by
unique(c(displayed,pointsinbounds()))
but it does not store it permanently - when I move out of France it removes France from the list. As well it reacts whenever
pointsinbounds()
change, while i want to react only when the total list changes, to avoid recalculation when you move back to the same country.
Any ideas?

Answer

Depending on what you want you may use one of two options.

1) To store the values into a reactiveValues().In this case the country list will be persistent during that session.

2) To store the value in a global value, and it will be persistent between sessions, and accessible between sessions/users (you can put the assignment in global.R).

EDIT: In option 2, added code to detect new countries.

Note: In the examples below I added a control to monitor the country list.

Example for option 1

library(shiny)
library(DT)
library(leaflet)
library(rgdal)
library(maptools)
library(rgeos)


ui <- fluidPage(leafletOutput("mymap") ,
                verbatimTextOutput('myCountries'))

countries <- readShapeSpatial("TM_WORLD_BORDERS_SIMPL-0.3.shp")

displayed <- c("United Kingdom")

server <- function(input, output, session) {
  r <- reactiveValues()


  observe({
    r$displayed <- unique(c(r$displayed, pointsInBounds())) 
  })

  output$mymap <- renderLeaflet({
    leaflet(countries) %>%
      addTiles() %>%
      setView(lng = -0.1294984,
              lat = 51.4992921,
              zoom = 10)
  })

  output$myCountries <- renderPrint({
    r$displayed 
  })


  pointsInBounds <- reactive({
    if (is.null(input$mymap_bounds)) {
      return(NULL)
    }
    bounds <- input$mymap_bounds
    N <- bounds$north
    S <- bounds$south
    E <- bounds$east
    W <- bounds$west
    BB = matrix(c(W, E, E, W, W, N, N, S, S, N), nrow = 5, ncol = 2)
    BB = SpatialPolygons(list(Polygons(list(Polygon(
      BB
    )), 1)))
    as.vector(countries[which(gIntersects(countries, BB, byid = TRUE)),]$NAME)
  })
} 

shinyApp(ui, server)

Example for option 2

library(shiny)
library(DT)
library(leaflet)
library(rgdal)
library(maptools)
library(rgeos)


ui <- fluidPage(leafletOutput("mymap") ,
                verbatimTextOutput('myCountries'))

countries <- readShapeSpatial("TM_WORLD_BORDERS_SIMPL-0.3.shp")

if (!exists('displayed')){
  displayed <<- c("United Kingdom")
}

server <- function(input, output, session) { 

  observe({   
    current.coutries <- pointsInBounds()
    new.countries    <- current.coutries[ ! (current.coutries %in% displayed) ]
    # DOWNLOAD new.countries
    displayed <<- unique(c(displayed, pointsInBounds()))   
  })

  output$mymap <- renderLeaflet({
    leaflet(countries) %>%
      addTiles() %>%
      setView(lng = -0.1294984,
              lat = 51.4992921,
              zoom = 10)
  })

  output$myCountries <- renderPrint({
    pointsInBounds()

    displayed 
  })

  observeEvent(input$mymap_bounds, {})
  pointsInBounds <- reactive({
    if (is.null(input$mymap_bounds)) {
      return(NULL)
    }
    bounds <- input$mymap_bounds
    N <- bounds$north
    S <- bounds$south
    E <- bounds$east
    W <- bounds$west
    BB = matrix(c(W, E, E, W, W, N, N, S, S, N), nrow = 5, ncol = 2)
    BB = SpatialPolygons(list(Polygons(list(Polygon(
      BB
    )), 1)))
    as.vector(countries[which(gIntersects(countries, BB, byid = TRUE)),]$NAME)
  })
} 

shinyApp(ui, server)