Geospatial Interaction

Idioms for interacting with geographic data

Code, Recording

library(tidyverse)
library(leaflet)
  1. Maps can be information dense, so it’s often useful to make them interactive. These notes review some basic strategies for interactive spatial data visualization.

  2. leaflet is an easy-to-use R package that’s often sufficient for routine visualization. It offers several types of marks (marks, circles, polygons) and allows them to encode fields in a dataset. Note that its interface is more like base R than ggplot2 — we specify each attribute in one plot command. For example, in the code block below, addTiles fetches the background map. addCircles overlays the new vector features on top of the map. It’s worth noting that the vector features were created automatically – there was no need to create or read in any type of sf object.

    cities <- read_csv("https://uwmadison.box.com/shared/static/j98anvdoasfb1h651qxzrow2ua45oap1.csv")
    leaflet(cities) %>%
      addTiles() %>%
      addCircles(
        lng = ~Long,
        lat = ~Lat,
        radius = ~sqrt(Pop) * 30
      )
    
  3. Leaflet maps can be embedded into Shiny apps using leafletOutput and renderLeaflet. For example, the Superzip Explorer is a visualization designed for showing income and education levels across ZIP codes in the US. In the server, the map is initialized using the leaflet command (without even adding any data layers).

    # Create the map
    output$map <- renderLeaflet({
      leaflet() %>%
        addTiles() %>%
        setView(lng = -93.85, lat = 37.45, zoom = 4)
    })
    
  4. The most interesting aspect of the explorer is that it lets us zoom into regions and study properties of ZIP codes within the current view. leaflet automatically creates an input$map_bounds input which is triggered anytime we pan or zoom the map. It returns a subset of the full dataset within the current view.

    zipsInBounds <- reactive({
      if (is.null(input$map_bounds)) return(zipdata[FALSE,]) # return empty data
      bounds <- input$map_bounds
      latRng <- range(bounds$north, bounds$south)
      lngRng <- range(bounds$east, bounds$west)
    
      # filters to current view
      subset(zipdata,
        latitude >= latRng[1] & latitude <= latRng[2] &
          longitude >= lngRng[1] & longitude <= lngRng[2])
    })
    

    Whenever this reactive is run, the histogram (output$histCentile) and scatterplot (output$scatterCollegeIncome) on the side of the app are updated.

  5. Notice that an observer was created to monitor any interactions with the map. Within this observe block, a leafletProxy call is used. This function makes it possible to modify a leaflet map without redrawing the entire map. It helps support efficient rendering – we’re able to change the colors of the circles without redrawing the entire leaflet view.

    leafletProxy("map", data = zipdata) %>%
      clearShapes() %>%
      addCircles(~longitude, ~latitude, radius=radius, layerId=~zipcode,
        stroke=FALSE, fillOpacity=0.4, fillColor=pal(colorData)) %>%
      addLegend("bottomleft", pal=pal, values=colorData, title=colorBy,
        layerId="colorLegend")
    
  6. We can often dynamically query spatial data. Querying a map can highlight properties of samples within a geographic region. For example, here is a map of in which each US county has been associated with a (random) pair of data features. Clicking on counties (or hovering with the shift key pressed) updates the bars on the right. Each bar shows the average from one of the data fields, across all selected counties.

  7. This linking is accomplished using event listeners. For example, the map includes the call .on("mouseover", update_selection), and update_selection changes the fill of the currently hovered county,

    svg.selectAll("path")
      .attr("fill", (d) => selected.indexOf(d.id) == -1 ? "#f7f7f7" : "#4a4a4a");
    

    The full implementation can be read here. Note that interactivity here is done just like in any other D3 visualization. We can treat the map as just another collection of SVG paths, and all our interaction events behave in the same way.

  8. We can also imagine selecting geographic regions by interacting with linked views. This is used in Nadieh Bremer’s Urbanization in East Asia visualization, for example, where we can see all the urban areas within a country by hovering its associated bar.

  9. Here is a somewhat more complex version of the earlier random data example where acounties are associated with (random) time series. Redrawing the lower and upper bounds on the time series changes which counties are highlighted.

  10. Though it’s not exactly interaction, another common strategy for spatiotemporal data is animation. The major trends often become apparent by visualizing the flow of visual marks on the screen. For example, how can we visualize where football players go on a field before they score a goal? One approach is to animate the trajectories leading up to the goal. Here is one beautiful visualization (in D3!) by Karim Douieb that shows the most common paths and the speed at which the players run.