This R markdown script can be used to easily visualize reindeer location data with different levels of information and effort in creating the map. This tutorial uses the leaflet R package to create interactive maps.
This is only necessary once.
# install.packages("sf")
# install.packages("leaflet")
# install.packages("dplyr")
These are the packages that are needed to create the maps.
library(sf) #work with location data like points
library(leaflet) #create interactive maps
library(dplyr) #to clean data
Commonly used base maps like Open Street Map are not optimized for Svalbard, so we first add our own base maps from the Norsk Polar institute (https://geodata.npolar.no/) as WMTS (Web Map Tile Services) to the leaflet interface. We also add a menu so we choose which base map we want to display in our map.
Tip: You can find more WMTS layers freely available online!
# Define WMTS URLs
svalbard_basemap_wmts <- "https://geodata.npolar.no/arcgis/rest/services/Basisdata/NP_Basiskart_Svalbard_WMTS_3857/MapServer/WMTS/tile/1.0.0/Basisdata_NP_Basiskart_Svalbard_WMTS_3857/default/default028mm/{z}/{y}/{x}.jpgpng"
svalbard_satellite_wmts <- "https://geodata.npolar.no/arcgis/rest/services/Basisdata/NP_Satellitt_Svalbard_WMTS_3857/MapServer/WMTS/tile/1.0.0/Basisdata_NP_Satellitt_Svalbard_WMTS_3857/default/default028mm/{z}/{y}/{x}.jpgpng"
# Create Leaflet map with multiple basemaps and overlays
topo_map <- leaflet() %>%
setView(lng = 15, lat = 78, zoom = 5) %>% # Center the initial view on Svalbard
# Add basemap options.
#These options are already included in the leaflet package
addProviderTiles(providers$Esri.WorldImagery, group = "Esri Satellite") %>%
addProviderTiles(providers$OpenStreetMap, group = "OpenStreetMap") %>%
addProviderTiles(providers$CartoDB.Positron, group = "CartoDB Light") %>%
# Now we add our own basemaps that we previously defined
# Add the Svalbard topogrpahic Basemap WMTS layer
addWMSTiles(
baseUrl = svalbard_basemap_wmts,
layers = "Basisdata_NP_Basiskart_Svalbard_WMTS_3857",
options = WMSTileOptions(format = "image/jpgpng", transparent = TRUE),
group = "Svalbard Basemap"
) %>%
# Add the Svalbard Satellite WMTS layer
addWMSTiles(
baseUrl = svalbard_satellite_wmts,
layers = "Basisdata_NP_Satellitt_Svalbard_WMTS_3857",
options = WMSTileOptions(format = "image/jpgpng", transparent = TRUE),
group = "Svalbard Satellite"
) %>%
# Here we add the menu
# Add layer control to switch between base layers and overlays
addLayersControl(
baseGroups = c("Esri Satellite", "OpenStreetMap", "CartoDB Light"),
overlayGroups = c("Svalbard Basemap", "Svalbard Satellite"),
options = layersControlOptions(collapsed = FALSE)
)
# Finally we plot the map, which was defined in the previous step
topo_map
The next step is to add our reindeer locations to the map. Therefore, we add a .CSV file with coordinates in the UTM system.
# Read the CSV file with the GPS data into R
data <- read.csv("data/BIG_faeces_loc_202406.csv")
Not all the coordinates were in the right number format so we filter them by the range of UTM coordinates that are possible.
# Clean up the UTM coordinates using parameter column names
valid_range <- list(
easting = c(400000, 900000),
northing = c(6500000, 9500000)
)
data_clean <- data %>%
filter(
!is.na(utm_easting) &
!is.na(utm_northing) &
utm_easting >= valid_range$easting[1] &
utm_easting <= valid_range$easting[2] &
utm_northing >= valid_range$northing[1] &
utm_northing <= valid_range$northing[2]
)
Then we extract all the coordinates and create a sf object. UTM coordinates do not work in leaflet so we have to project to a geographic coordinate system, here WGS84.
# Create an sf object with UTM coordinates (EPSG:25833)
data_sf <- st_as_sf(data_clean, coords = c("utm_easting", "utm_northing"), crs = 25833)
# Reproject the coordinates to EPSG:4326 for use in leaflet
data_projected <- st_transform(data_sf, crs = 4326)
Adding the points to leaflet is not straight forward. First we have to extract both the longitude and latitude and save it in their own variable.
Then we add the points via a loop.
You can play around with the parameters in the for loop to change
e.g. colors, size, opacity etc. This function can also be used to add
single locations to the map if you replace the lng = lng[i]
and lat = lat[i]
with geographic coordinates.
The popup =
adds a popup window when you click on the
map. The Infos have to fit to the headers of the input data.
Double check this if you have any issues. You can check it in R with the
function names(data_projected)
.
# Extract the longitude and latitude
lng <- st_coordinates(data_projected)[, 1]
lat <- st_coordinates(data_projected)[, 2]
# Create a copy of the base map
reindeer_map <- topo_map
# Add markers to the Leaflet map
for (i in 1:length(lng)) {
reindeer_map <- reindeer_map %>%
addCircleMarkers(
lng = lng[i],
lat = lat[i],
radius = 2,
color = "white",
fill = TRUE,
fillColor = "grey",
fillOpacity = 0.7,
opacity = 1,
popup = paste(
"Infos:",
"<br>Date:", data_projected[["date..yyyy.mm.dd."]][i],
"<br>Sex:", data_projected[["reindeer_sex"]][i],
"<br>Age:", data_projected[["reindeer_age"]][i]
)
)
}
# Step 8: Plot the map
reindeer_map
To make the the labels look even nicer you can integrate some HTML/CSS code. You can play around which parameters you can change and look for sections that are based on the headings !
# Create stylish labels with HTML/CSS
label_text <- lapply(1:nrow(data_projected), function(i) {
htmltools::HTML(paste0(
"<div style='font-family: Arial, sans-serif; padding: 6px;'>
<div style='background-color: #2c3e50; color: white; padding: 4px 8px;
border-radius: 3px 3px 0 0; font-weight: bold;'>
Observation ", i, "
</div>
<div style='padding: 6px; border: 1px solid #ddd; border-top: none;
border-radius: 0 0 3px 3px; background-color: #f9f9f9;'>
<b>Date: </b>", data_projected[["date..yyyy.mm.dd."]][i], "<br/>
<b>Sex: </b>", data_projected[["reindeer_sex"]][i], "<br/>
<b>Age: </b>", data_projected[["reindeer_age"]][i], "<br/>
<b>Coordinates:</b><br/>
<span style='font-size: 0.9em; color: #555;'>
", round(lat[i], 4), "°N, ", round(lng[i], 4), "°E
</span>
</div>
</div>"
))
})
To visualize the characteristics of an observed reindeer we can change the colours of the points accordingly. Here we use the sex of the reindeer as an example.
You can always change the colors or what it should display. Keep the
advanced_reindeer_markers_map <- topo_map
# Define fixed colors for each sex
sex_colors <- c(
"male" = "green",
"female" = "yellow",
"unknown" = "#95a5a6"
)
The following section adds the points in the for loop but this time it checks the sex column for the point it is producing. Then it compares it to the colors that we defined.
#
for (i in 1:length(lng)) {
current_sex <- data_projected[["reindeer_sex"]][i]
marker_color <- ifelse(current_sex %in% names(sex_colors),
sex_colors[current_sex],
"#95a5a6")
advanced_reindeer_markers_map <- advanced_reindeer_markers_map %>%
addCircleMarkers(
lng = lng[i],
lat = lat[i],
radius = 5,
color = "white",
fillColor = marker_color,
fillOpacity = 0.8,
weight = 1.5,
popup = label_text[[i]],
label = paste("Observation", i)
)
}
# Add legend for sex colors
advanced_reindeer_markers_map <- advanced_reindeer_markers_map %>%
addLegend(
position = "bottomright",
colors = sex_colors,
labels = names(sex_colors),
title = "Reindeer Sex",
opacity = 0.8
)
# Output the map
advanced_reindeer_markers_map
The following map is a combination of all our previous maps and scripts. SO then we have a map that: - Have different base maps that can be selected - Shows the location of the observed reindeer - Has a nice popup window which gives information about the reindeer - Colored points that indicate the sex of the reindeer and a legend explaing the coloring
# Create stylish labels with HTML/CSS
label_text <- lapply(1:nrow(data_projected), function(i) {
htmltools::HTML(paste0(
"<div style='font-family: Arial, sans-serif; padding: 6px;'>
<div style='background-color: #2c3e50; color: white; padding: 4px 8px;
border-radius: 3px 3px 0 0; font-weight: bold;'>
Observation ", i, "
</div>
<div style='padding: 6px; border: 1px solid #ddd; border-top: none;
border-radius: 0 0 3px 3px; background-color: #f9f9f9;'>
<b>Date: </b>", data_projected[["date..yyyy.mm.dd."]][i], "<br/>
<b>Sex: </b>", data_projected[["reindeer_sex"]][i], "<br/>
<b>Age: </b>", data_projected[["reindeer_age"]][i], "<br/>
<b>Coordinates:</b><br/>
<span style='font-size: 0.9em; color: #555;'>
", round(lat[i], 4), "°N, ", round(lng[i], 4), "°E
</span>
</div>
</div>"
))
})
# Define fixed colors for each sex
sex_colors <- c(
"male" = "green",
"female" = "yellow",
"unknown" = "#95a5a6" # Fixed spelling from "unkown" to "unknown"
)
# Create advanced map with sex-based coloring
advanced_reindeer_markers_map <- topo_map
for (i in 1:length(lng)) {
current_sex <- data_projected[["reindeer_sex"]][i] # Fixed to use params
marker_color <- ifelse(current_sex %in% names(sex_colors),
sex_colors[current_sex],
"#95a5a6")
advanced_reindeer_markers_map <- advanced_reindeer_markers_map %>%
addCircleMarkers(
lng = lng[i],
lat = lat[i],
radius = 5,
color = "white",
fillColor = marker_color,
fillOpacity = 0.8,
weight = 1.5,
popup = label_text[[i]],
label = paste("Observation", i)
)
}
# Add legend for sex colors
advanced_reindeer_markers_map <- advanced_reindeer_markers_map %>%
addLegend(
position = "bottomright",
colors = sex_colors,
labels = names(sex_colors),
title = "Reindeer Sex",
opacity = 0.8
)
# Output the map
advanced_reindeer_markers_map
Using the function sessionInfo()
gives insight into the
R version, platform, packages and local settings to increase
reproducibility and is good practice to include at the end of
scripts.
sessionInfo()
## R version 4.4.1 (2024-06-14 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 22631)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=German_Germany.utf8 LC_CTYPE=German_Germany.utf8
## [3] LC_MONETARY=German_Germany.utf8 LC_NUMERIC=C
## [5] LC_TIME=German_Germany.utf8
##
## time zone: Europe/Berlin
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] dplyr_1.1.4 leaflet_2.2.2 sf_1.0-18
##
## loaded via a namespace (and not attached):
## [1] vctrs_0.6.5 cli_3.6.3 knitr_1.48
## [4] rlang_1.1.4 xfun_0.49 DBI_1.2.3
## [7] KernSmooth_2.23-24 generics_0.1.3 jsonlite_1.8.8
## [10] glue_1.7.0 htmltools_0.5.8.1 e1071_1.7-16
## [13] sass_0.4.9 rmarkdown_2.29 grid_4.4.1
## [16] tibble_3.2.1 crosstalk_1.2.1 evaluate_1.0.1
## [19] jquerylib_0.1.4 classInt_0.4-10 fastmap_1.2.0
## [22] yaml_2.3.10 lifecycle_1.0.4 compiler_4.4.1
## [25] pkgconfig_2.0.3 htmlwidgets_1.6.4 Rcpp_1.0.12
## [28] rstudioapi_0.17.1 leaflet.providers_2.0.0 digest_0.6.36
## [31] R6_2.5.1 tidyselect_1.2.1 class_7.3-22
## [34] pillar_1.10.1 magrittr_2.0.3 bslib_0.8.0
## [37] tools_4.4.1 proxy_0.4-27 units_0.8-5
## [40] cachem_1.1.0