My first interactive map with Shiny

Shiny is an amazing R package that makes it easy to build interactive web applications. Combined with RStudio it becomes a powerful tool allowing you to design, test and finally deploy your application on the web. In addition, you can freely host your app on the shinyapps server. I discovered Shiny one year ago, while working on a method to generate weights of ordered weighted averaging aggregation operators. I usually put all my scripts online, but in this work I wanted a more user-friendly way of sharing information. This is why I got interested into Shiny and developed my first web application. This application is very basic but it gave me the opportunity to learn more about Shiny and how I can use it as researcher to reach a broader audience by developing vizualization tools along with my academic research articles. In this post, I’ll present you a few things I learned while developping my first interactive maps with Shiny.

Context

This interactive web application has been designed to provide an easy-to-use interface to visualize socio-ecological interactions at different scales in 16 case studies across Europe. It was developed as part of a research project funded by the ALTER-Net network through the AHIA funding instrument. In this project, we define a socio-ecological interaction as the presence of an individual in a given zone of a site during a given hour. Socio-ecological interactions have been extracted from a Flickr database containing more than 150,000 photographs taken by about 2,000 users between 2000 and 2017 in the 16 case study sites. A script (in R) showing how to get the list of public photos for a list of Flickr users is available here. Each photograph has been manually validated and classified in ordrer to identify different types of interactions (landscape, recreational activity…). In practice, I divided every site in zones using square grid cells with a side length of 500 meters. See this post and/or this one to know more about the creation of spatial grids with R and how to combine R with postGIS to efficiently intersect spatial objects. I also identified the place of residence of every users based on their Flickr timeline using a 100 x 100 km^2 world grid cells with a code “Most Frequented Locations” available on my website. Finaly, Flickr users have been separated into two groups (locals & visitors) according to the distance between their place of residence and the sites.

At the end of the process we obtain a multiscale socio-ecological network composed of 7,354 socio-ecological interactions from 365 distinct places of residence all over the world to 3,418 grid cells located in 16 study sites.

 

 

The source code of the app is available on my website.

Import data

First, we need to import our input data into the visualization process. A Shiny app is composed of two R files, ui.R which controls the user interface and server.R which basically contains all the functions and instructions needed to display the maps, plots and everything you want in the user interface. The directory in which you save the server and ui R scripts is the working directory of your Shiny app. Note that by convention, all the elements that will be rendered in the web browser (images, html…) must be stored in a WWW directory located in your working directory.

server.R

# Load data
load(file = "Network.RData")
load(file = "Spatial.RData")
load(file = "Temporal.RData")
load(file = "Interactions.RData")

In this case, I wanted to focus on four aspects of the multiscale socio-ecological network: a representation of the spatial network at a world scale, a visualization of the spatial & temporal distribution of interactions per site, and, finally, a representation of the type of interactions (recreational activities and type of landscapes). I cleaned and preprocessed my data with R and save them in .Rdata, one file per vizualization that I displayed in separate tabs. I tried to clean and reduce the data as much as possible in order to avoid unnecessary computations that may increase page load time.

Theme, navbarPage & tabPanel

Let’s now focus on the structure of the app that was largely inspired by the SuperZIP demo. As mentioned above, I chose to divide the app in distinct navigable components focusing on different aspects of the network using the navbarPage() and tabPanel() functions. I opted for the cerulean theme. Note that it is also possible to include a title that will appear in the browser toolbar using the argument windowTitle of navbarPage(). I don’t remember why but I didn’t manage to add a logo in navbarPage() so I used the code below to display the Logo.jpg (store in the WWW folder) in the browser toolbar.

ui.R

navbarPage(title=HTML('<span style="font-size:120%;color:white;font-weight:bold;">Multi-scale socio-ecological interactions&nbsp;&nbsp;</span></a>'),
           theme = shinytheme("cerulean"),
           windowTitle = "AHIA App",
           header = "",
           
  tabPanel(strong("Network"),
    div(class="outer",     
      
      # Include custom CSS & logo   
      tags$head(
        includeCSS("styles.css"),
        tags$link(rel = "icon", type = "image/png", href = "Logo.jpg")
      ),

It is also possible to include a css file to custom your panels and layout your app. Here, I modified the style.css file used in the SuperZIP demo to obtain different styles of panel (color, opacity…).

Maps & Leaflet

To visualize the nework at a world scale (but also the spatial distribution of interactions in each site) I used renderLeaflet() to create a base map,

server.R

output$mapnet=renderLeaflet({

    leaflet() %>%
      addTiles() %>%
      fitBounds(-8.865578,29.048034,29.334944,66.491464)  #%>%

})

that is refreshed with each update using leafletProxy() call from an observer using the observe() function.

server.R

observe({

    net=net()
    toi=net$toi
    fromi=net$fromi
    linki=net$linki

    leafletProxy("mapnet") %>%
      clearShapes() %>%
      clearMarkers() %>%
      addPolylines(data = linki, color = "green", opacity = 0.3, weight = 1.5, stroke=T) %>%
      addCircleMarkers(lng = fromi[,2], lat=fromi[,3], radius=6, stroke=FALSE,
                       color = "steelblue", opacity = 0.75, fill = TRUE, fillOpacity = 0.75) %>%
      addCircleMarkers(lng = toi[,3], lat=toi[,4], popup=toi[,2], radius=10, stroke=FALSE,
                       color = "#CC6666", opacity = 0.75, fill = TRUE, fillOpacity = 0.75)


})

To do so, I used many reactive expressions that can be read and called from an observer. If an input is used in a reactive expression, it will be run each time a user changes this input value from the user interface. Then, if this reactive expression is called from an observer the map will be changed accordingly. In the example above, I created a reactive expression net() returning the network attributes (nodes and links) according to the case study site (CSnet in the ui). I also created a base map mapnet that will be updated each time a user changes the value of CSnet in the user interface.

This helps me to play with the different layers of my spatial objects and to decide which of them would be refreshed according to the type of changes made from the user interface.

The spatial distribution of socio-ecological interactions in each case study site is available from the second tab called Spatial Distribution. By default, the map shows all the cells with at least one interaction made by all types of users in the province of Barcelona. Different scales are available and it is also possible to change the type of interactions according to the user’s origin. I used the popup argument of addPolygons() to add popups to the map.

server.R

# Popup
popupij=paste0("<span style='color: #7f0000'><strong>#Interactions </strong></span>",
                   shpij@data$NbInt, 
                   "<br><span style='color: salmon;'><strong>#Locals </strong></span>", 
                   shpij@data$NbIntLocal,
                   "<br><span style='color: salmon;'><strong>#Visitors </strong></span>", 
                   shpij@data$NbIntVisitor 
)

I also included a checkboxInput() to allow the users to display a heatmap showing the number of interactions (all, locals, visitors and ratio locals/all) instead of the default map.

ui.R

# Heatmap
div(" ", style="height:40px;"),
checkboxInput("heatmap", "Display heatmap",value=FALSE),

Reset & default view buttons

I decided to include reset & default view buttons in order to ease the navigation, particularly useful to quickly zoom out.

server.R

# Reset view button
observe({
    
    if(input$reset_button_sp){
    
      shpi=shpi()
      
      # Bounds
      minlon=bbox(shpi)[1,1]
      minlat=bbox(shpi)[2,1]
      maxlon=bbox(shpi)[1,2]
      maxlat=bbox(shpi)[2,2]
      
      # Map
      leafletProxy("mapsp") %>%
        fitBounds(minlon,minlat,maxlon,maxlat)
      
    }
    
})
  
# Default values button
observe({
    
    if(input$default_values_sp){
    
      updateSelectInput(session, "CSsp", selected = 1)
      updateNumericInput(session, "l", value = 4)
      updateNumericInput(session, "typesp", value = 1)
      updateCheckboxInput(session, "heatmap", value=FALSE)
      
      # Map
      leafletProxy("mapsp") %>%
        fitBounds(1.360428,41.192649,2.778114,42.323340)
    }
    
})

ui.R

# Reset & default buttons
div(" ", style="height:20px;"),
                    
actionButton("reset_button_sp", "Reset view"),
actionButton("default_values_sp", "Default values"),
tags$style(type='text/css', "#reset_button_sp {width:130px;float:left;}"),
tags$style(type='text/css', "#default_values_sp {width:130px;float:right;}"),

3D explorer

Finally, I added a 3D EXPLORER button displaying a 3D vizualization for each case study using functions of the shinyjs package. The 3D vizs are made with QGIS with the Qgis2threejs plugin and are stored in the WWW folder.

server.R

csid=reactive({
    
    #Case study
    csid=shp[shp@data[,2]==as.numeric(input$CSsp) & shp@data[,3]==0,]
    csid=as.character(csid@data[1,1])
    csid=paste("./3D/", csid, "/", csid, "_3D.html", sep="")
    csid
    
})

server.R

# 3D button
onclick("tdexplorer", runjs(paste("window.open(", "'", csid(), "'", ",'_blank'",")", sep="")))

ui.R

# 3D
div(" ", style="height:20px;"),
useShinyjs(),
actionButton("tdexplorer", "3D EXPLORER"),
tags$style(type='text/css', "#tdexplorer {width:285px;text-align:center;}")

If you are interested in this work and would like to know more about the methodology, a preprint of the paper is available here.

comments powered by Disqus