Today I wanted to look up the water quality at a destination I will be spending a day during the current massive heatwave. Luckily the European Environment Agency (EAA) publishes quite extensive data on bathing water quality in the whole EU. But I didn’t find a good dashboard like for example for the Austrian data reported to the same system or a map. But the data from the EAA can be downloaded from their website, so why not make a quick map myself. The info page of the dataset can be found here, the page also contains links to the metadata and dataset download.
We are gonna need some packages sf for wrangling geodata, mapgl to display a map, readxl to read the data, tidyr, stringr and dplyr for general data-wrangling and finally glue to create some html output from templates and data.
library(sf)
library(mapgl)
library(readxl)
library(tidyr)
library(stringr)
library(dplyr)
library(glue)
Reading the data is easy, the dataset is luxuriously well formatted, in long format, no data cleaning necessary.
bw_assessment_raw <- read_excel("bw_assessment_eea_datahub_1990_2025.xlsx")
Then we set a parameter for the last year and define the colors we will use to color-code the water-quality values. To get a list of all values that occur in the dataset I used the commented out line.
last_year <- 2025
# bw_assessment_raw |> pull(quality) |> unique() |> sort() |> dput()
quality_colors <- c("#999", "#0c0", "#161", "#dd0", "#d70", "#b00") |>
setNames(c("0 - Not classified", "1 - Excellent", "2 - Good", "3 - Good or Sufficient",
"3 - Sufficient", "4 - Poor"))
Now we summarise the dataset to one line per bathing spot and filter to get only the spots where an assessment from last year is available. We also create some html code to be displayed on hover and on click in the map. In the popup we have the name, quality last year (color coded) followed by the quality in all previous years where an assessment is available, in the tooltip we just show the name of the spot color-coded according to the quality in the last year.
points <- bw_assessment_raw |>
group_by(bathingWaterIdentifier) |>
arrange(desc(season)) |>
filter(any(season==last_year)) |>
summarise(
quality_last = quality[season==last_year],
lon=first(lon),
lat=first(lat),
bathingWaterName = first(bathingWaterName),
popup_html = glue(
"<div style='color:black;'>
<h3>{bathingWaterName}</h3>
<bold>Quality {last_year}:</bold> <span style='color:{quality_colors[quality_last]};'>{quality_last}</span><br>
Quality previous years:
<ul>
{str_c('<li><bold>', season[season!=last_year], ':</bold> <span style=\"color:', quality_colors[quality[season!=last_year]], '\">', quality[season!=last_year], '</span></li>', collapse='\n')}
</ul>
</div>
"),
tooltip_html = glue(
"<span style='color:{quality_colors[quality_last]};'>{bathingWaterName}</span>"
)
) |>
st_as_sf(coords=c("lon", "lat"), crs=st_crs("WGS84"))
All left to do is to display the map. Color values have to be passed in a bit of a strange way to add_circle_layer but all in all it’s uper straight forward. Of course we also add an attribution text for the data including a link to the data website. This will be displayed in addition to the attribution to Carto and OpenStreetmap.
maplibre(
center=c(10, 45),
zoom=4,
attributionControl=list(customAttribution="<a href='https://www.eea.europa.eu/en/datahub/datahubitem-view/c3858959-90da-4c1b-b9ca-492db0e514df?activeAccordion=1102379' style='color:black;'>Data: CC-BY, EAA: Bathing Water Directive - Status of bathing water</a>")
) |>
add_circle_layer(
id="testlayer",
source=points,
circle_color=match_expr(
"quality_last",
values=names(quality_colors),
stops=unname(quality_colors)
),
popup="popup_html",
tooltip="tooltip_html"
) |>
add_fullscreen_control() |>
add_navigation_control()
To give the user a choice not to load the map including 3rd party ressources I saved the widget with htmlwidgets::saveWidget and added it to be loaded optionally in the iframe below. Enjoy.