25  Trajectory Analysis

Transforming static trajectory plots into interactive HTML widgets

Author

Jack Davison

Abstract
openair already contains functionality for visualising HYSPLIT back trajectories, but as more trajectory paths are added these static plots can become difficult to interpret. openairmaps allows for effectively the same visualisations to be mapped in leaflet, which can help untangle these complex figures.

25.1 Data

openairmaps contains the traj_data dataset to allow users to test the directional analysis functions. The structure of this data set is provided below. This was obtained using the importTraj() function in openair; if you are using your own trajectory data, try to match this structure (including column names) so that the openairmaps functions will work correctly.

library(openairmaps)
dplyr::glimpse(traj_data)
Rows: 5,432
Columns: 17
$ date     <dttm> 2010-04-15, 2010-04-15, 2010-04-15, 2010-04-15, 2010-04-15, …
$ receptor <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ year     <dbl> 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2…
$ month    <int> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4…
$ day      <int> 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 1…
$ hour     <int> 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9,…
$ hour.inc <dbl> 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -1…
$ lat      <dbl> 51.500, 51.692, 51.879, 52.063, 52.246, 52.431, 52.616, 52.79…
$ lon      <dbl> -0.100, 0.139, 0.378, 0.618, 0.859, 1.102, 1.343, 1.580, 1.81…
$ height   <dbl> 10.0, 10.4, 10.5, 10.5, 10.4, 10.2, 9.9, 9.7, 9.8, 10.2, 10.9…
$ pressure <dbl> 1013.5, 1014.2, 1014.8, 1015.2, 1015.6, 1015.9, 1016.1, 1016.…
$ date2    <dttm> 2010-04-15 00:00:00, 2010-04-14 23:00:00, 2010-04-14 22:00:0…
$ nox      <dbl> 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1…
$ no2      <dbl> 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1…
$ o3       <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 7…
$ pm2.5    <dbl> 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 2…
$ pm10     <dbl> 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2…

25.2 Overview

There are two all-in-one mapping functions for trajectory analysis in openairmaps, which will be discussed in turn.

  1. trajMap(), which is an analogue to openair::trajPlot().

  2. trajLevelMap(), which is an analogue to openair::trajLevel().

Both of these functions require the data to be in the structure shown in the previous section.

25.3 Mapping Trajectory Paths

By default, trajMap() will plot your trajectories in black on a default “OpenStreetMap” base map, but the colour argument allows you to colour by any column of your data. For example, Figure 25.1 shows the trajectory paths coloured by arrival date. A popup is also automatically generated — try clicking on the points to get information about that particular air mass.

trajMap(traj_data, colour = "date")
Figure 25.1: A simple demonstration of trajMap.

What may be of greater interest is colouring the trajectories by the concentration of pollutant on their arrival. The colour argument can be used for this. When colour is specified, trajectory paths are plotted in order of the numeric value of the “colour” column, so the highest concentrations (which are assumed to be of most interest) are most easily accessible. Specifying colour also creates a legend and appends a relevant line to the popup — explore the example in Figure 25.2.

trajMap(
  traj_data,
  colour = "pm10",
  npoints = 6,
  cols = "magma",
  provider = "CartoDB.Positron"
)
Figure 25.2: A more thorough application of trajMap, this time coloured by PM concentrations.

There is a control option in trajMap() which allows for a layer control menu to be generated. This might be useful if you have a lot of trajectory paths and want to let users reduce the clutter. It also pairs nicely with clustered trajectories returned by openair::trajCluster(). An example of this is given below, in Figure 25.3.

clustdata <- openair::trajCluster(traj_data)
trajMap(
  data = clustdata$data$traj,
  colour = "cluster",
  control = "cluster",
  cols = c("#9B5DE5", "#F15BB5", "#FEE440", "#00BBF9", "#00F5D4"),
  provider = "CartoDB.DarkMatter"
)
Figure 25.3: Interactive map of clustered trajectories, with a cool neon colour scheme!

25.4 Mapping Gridded Trajectories

trajLevelMap() is the other all-in-one trajectory function and is, in many ways, simpler than trajMap(). Think of trajLevelMap() as you do openair::trajLevel() — it has many of the same arguments and does effectively the same thing, but returns an interactive map. In Figure 25.4, try hovering over and clicking each of the tiles to learn more specific details about each bin.

trajLevelMap(
  traj_data,
  statistic = "pscf",
  pollutant = "pm10",
  cols = "viridis",
  provider = "Esri"
)
Figure 25.4: An interactive ‘frequency’ map plotted by trajLevelMap.

At time of writing, all of the different trajLevel() statistics are supported in trajLevelMap().

25.5 Marker Function

The marker function equivalent of trajMap() is addTrajPaths() which, much like addPolarMarkers(), allows you to customise your trajectory maps. For example, Figure 25.5 shows a way to plot multiple sets of trajectory data on one map (in this case, one arriving in London and the other in Paris).

library(leaflet)
library(openair)

france <- importTraj("paris", year = 2009) %>%
  selectByDate(
    start = "15/4/2009",
    end = "21/4/2009"
  )

uk <- importTraj(year = 2009) %>%
  selectByDate(
    start = "15/4/2009",
    end = "21/4/2009"
  )

leaflet() %>%
  addTiles() %>%
  addTrajPaths(data = uk,
               color = "blue",
               group = "London, UK", 
               opacity = .25) %>%
  addMarkers(data = dplyr::slice_head(uk, n = 1),
             lat = ~lat, lng = ~lon,
             group = "London, UK", label = "UK") %>%
  addTrajPaths(data = france,
               color = "red",
               group = "Paris, France", 
               opacity = .25) %>%
  addMarkers(data = dplyr::slice_head(france, n = 1), 
             lat = ~lat, lng = ~lon,
             group = "Paris, France", label = "FR") %>%
  addLayersControl(overlayGroups = c("Paris, France", "London, UK"))