De Knegt and colleagues studied habitat selection by African elephants in Kruger National Park (KNP), South Africa’s largest nature reserve, covering roughly 19,000 km2 and harbouring close to 14,000 African elephants (Loxodonta africana). 33 elephants (19 females and 14 males) were tagged with GPS collars. Locations were recorded at hourly intervals over a three‐year period (2005–2008). In this report, I show how R can be used to decipher and the coded GPS records and be visualized on a map, using a subset of the data of a subset of individuals.
The dataset that I used contains tracking data from 2 individuals, with 798 fixes in total. Each fix consists of 3 variables: one number (column timestamp) and two strings (columns id and payload).
Datetime objects are often stored in a UNIX timestamp format: a number that represents the number of seconds that passed since midnight of January 1, 1970, GMT time. With the package lubridate, these numbers can easily be converted into readable dates and times. Here we save them as new variable dttm.
dat <- dat %>%
mutate(dttm = parse_date_time("1970-1-1 0:0:00",
orders = "%Y-%m-%d %H:%M:%S",
tz = "GMT") + timestamp)
dat
## # A tibble: 798 x 4
## id timestamp payload dttm
## <chr> <dbl> <chr> <dttm>
## 1 am72 1151064476 017202f979afdb4cfb 2006-06-23 12:07:56
## 2 am72 1151066276 016e02f9b55fdb508b 2006-06-23 12:37:56
## 3 am72 1151068076 016302f9bd1fdb512e 2006-06-23 13:07:56
## 4 am72 1151069876 015f02f9cc5fdb50c6 2006-06-23 13:37:56
## 5 am72 1151071675 015002f9c98fdb508e 2006-06-23 14:07:55
## 6 am72 1151073475 014102f9cc9fdb5065 2006-06-23 14:37:55
## 7 am72 1151075275 012b02f9ccbfdb509b 2006-06-23 15:07:55
## 8 am72 1151077075 011902f9b40fdb51e7 2006-06-23 15:37:55
## 9 am72 1151078875 010602f9a6afdb5360 2006-06-23 16:07:55
## 10 am72 1151080675 00f702f9a2ffdb53be 2006-06-23 16:37:55
## # ... with 788 more rows
The payload is a text string composed of a series of codes, called nibbles.
Using the str_sub
function, we can break up the payload
string as follows:
dat <- dat %>%
mutate(temp_hex = str_sub(payload, start = 1, end = 4),
lon_hex = str_sub(payload, start = 5, end = 11),
lat_hex = str_sub(payload, start = 12, end = 18)) %>%
select(-c(timestamp,payload))
dat
## # A tibble: 798 x 5
## id dttm temp_hex lon_hex lat_hex
## <chr> <dttm> <chr> <chr> <chr>
## 1 am72 2006-06-23 12:07:56 0172 02f979a fdb4cfb
## 2 am72 2006-06-23 12:37:56 016e 02f9b55 fdb508b
## 3 am72 2006-06-23 13:07:56 0163 02f9bd1 fdb512e
## 4 am72 2006-06-23 13:37:56 015f 02f9cc5 fdb50c6
## 5 am72 2006-06-23 14:07:55 0150 02f9c98 fdb508e
## 6 am72 2006-06-23 14:37:55 0141 02f9cc9 fdb5065
## 7 am72 2006-06-23 15:07:55 012b 02f9ccb fdb509b
## 8 am72 2006-06-23 15:37:55 0119 02f9b40 fdb51e7
## 9 am72 2006-06-23 16:07:55 0106 02f9a6a fdb5360
## 10 am72 2006-06-23 16:37:55 00f7 02f9a2f fdb53be
## # ... with 788 more rows
Then, to make clear that these are not numeric data, we add the prefix 0x, as follows.
dat <- dat %>%
mutate(temp_hex = str_c("0x",temp_hex,sep=""),
lon_hex = str_c("0x",lon_hex,sep=""),
lat_hex = str_c("0x",lat_hex,sep=""))
dat
## # A tibble: 798 x 5
## id dttm temp_hex lon_hex lat_hex
## <chr> <dttm> <chr> <chr> <chr>
## 1 am72 2006-06-23 12:07:56 0x0172 0x02f979a 0xfdb4cfb
## 2 am72 2006-06-23 12:37:56 0x016e 0x02f9b55 0xfdb508b
## 3 am72 2006-06-23 13:07:56 0x0163 0x02f9bd1 0xfdb512e
## 4 am72 2006-06-23 13:37:56 0x015f 0x02f9cc5 0xfdb50c6
## 5 am72 2006-06-23 14:07:55 0x0150 0x02f9c98 0xfdb508e
## 6 am72 2006-06-23 14:37:55 0x0141 0x02f9cc9 0xfdb5065
## 7 am72 2006-06-23 15:07:55 0x012b 0x02f9ccb 0xfdb509b
## 8 am72 2006-06-23 15:37:55 0x0119 0x02f9b40 0xfdb51e7
## 9 am72 2006-06-23 16:07:55 0x0106 0x02f9a6a 0xfdb5360
## 10 am72 2006-06-23 16:37:55 0x00f7 0x02f9a2f 0xfdb53be
## # ... with 788 more rows
The last step consists of converting the hexadecimal codes to integers and divide these by 1e5, to obtain latitude and longitude.
dat <- dat %>%
mutate(lon = map_dbl(lon_hex, hex2integer) / 1e5,
lat = map_dbl(lat_hex, hex2integer) / 1e5)
dat
## # A tibble: 798 x 7
## id dttm temp_hex lon_hex lat_hex lon lat
## <chr> <dttm> <chr> <chr> <chr> <dbl> <dbl>
## 1 am72 2006-06-23 12:07:56 0x0172 0x02f979a 0xfdb4cfb 31.2 -24.1
## 2 am72 2006-06-23 12:37:56 0x016e 0x02f9b55 0xfdb508b 31.2 -24.0
## 3 am72 2006-06-23 13:07:56 0x0163 0x02f9bd1 0xfdb512e 31.2 -24.0
## 4 am72 2006-06-23 13:37:56 0x015f 0x02f9cc5 0xfdb50c6 31.2 -24.0
## 5 am72 2006-06-23 14:07:55 0x0150 0x02f9c98 0xfdb508e 31.2 -24.0
## 6 am72 2006-06-23 14:37:55 0x0141 0x02f9cc9 0xfdb5065 31.2 -24.0
## 7 am72 2006-06-23 15:07:55 0x012b 0x02f9ccb 0xfdb509b 31.2 -24.0
## 8 am72 2006-06-23 15:37:55 0x0119 0x02f9b40 0xfdb51e7 31.2 -24.0
## 9 am72 2006-06-23 16:07:55 0x0106 0x02f9a6a 0xfdb5360 31.2 -24.0
## 10 am72 2006-06-23 16:37:55 0x00f7 0x02f9a2f 0xfdb53be 31.2 -24.0
## # ... with 788 more rows
Now that we have converted the data from hexadecimal representation
into decimal representation, we can plot the elephant trajectories on a
dynamic leaflet
map, plotting a separate line for each
individual. We will also show different base layers: the default
open-streetmap layer, as well as the ESRI world imagery data. We will
add a menu where you can toggle the individuals as well as the base
layer.
library(leaflet)
leaflet(dat) %>%
addTiles(group = "default") %>%
addTiles(urlTemplate = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png",
attribution = "ESRI world imagery",
group = "ESRI world imagery") %>%
# Add separate lines
addPolylines(lng = ~lon, lat = ~lat, color = "#ff0000", group = "am72",
data = filter(dat, id == "am72")) %>%
addPolylines(lng = ~lon, lat = ~lat, color = "#0000ff", group = "am160",
data = filter(dat, id == "am160")) %>%
# Layers control
addLayersControl(
baseGroups = c("default", "ESRI world imagery"),
overlayGroups = c("am72", "am160"),
options = layersControlOptions(collapsed = FALSE)
)
The map shows …