edr4r

CRAN status R-CMD-check Codecov test coverage Lifecycle: experimental License: MIT

An R client for any service that speaks OGC API - Environmental Data Retrieval (EDR). The spec is general, but in practice this package gets the most use against in-situ monitoring networks — stream gauges, weather stations, snow telemetry, reservoir telemetry — that expose their stations and time series through EDR.

Two known-good places to point it:

For cross-server experiments, the Met Office Labs EDR demonstrator is another useful endpoint. It is a technical demonstrator, not an operational service: availability, collections, and response details can change without notice, so do not build production workflows around it.

The goal is to take the tedious parts of EDR off your hands — URL construction, comma-separated parameter lists, WKT coordinate encoding, retries, content negotiation — and hand back something you can actually do data analysis with:

Installation

Install the released version from CRAN:

install.packages("edr4r")

Or the development version from GitHub:

# install.packages("pak")
pak::pak("ksonda/edr4r")

# or
# install.packages("remotes")
remotes::install_github("ksonda/edr4r")

For local development:

git clone https://github.com/ksonda/edr4r.git
cd edr4r
R -e 'devtools::install()'

Requires R >= 4.1. The sf package is optional but recommended (used to turn location lists and GeoJSON into spatial objects).

Quick start

Start by pointing a client at a server. The base URL is the only thing it really needs:

library(edr4r)

client <- edr_client("https://api.waterdata.usgs.gov/ogcapi/beta")
# or "https://api.wwdh.internetofwater.app"
# or "http://localhost:5005" if you're running pygeoapi locally

edr_collections(client)
#> # A tibble: N × 7
#>   id                   title                description  extent_bbox crs   data_queries links
#>   <chr>                <chr>                <chr>        <list>      <chr> <list>       <list>
#> 1 monitoring-locations Monitoring locations ...          <dbl [4]>   ...   <chr [3]>    ...
#> 2 daily-values         Daily values         ...          <dbl [4]>   ...   <chr [3]>    ...
#> ...

The collection IDs above (monitoring-locations, daily-values) are the ones I used as placeholders — every server advertises its own. The first thing to do against a new service is run edr_collections() and read the data_queries column to see which EDR endpoints each collection supports.

To try the non-operational Met Office demonstrator with a deliberately small request, query one terrain point rather than a forecast collection:

met <- edr_client(
  "https://labs.metoffice.gov.uk/edr",
  timeout = 10,
  max_tries = 1
)

terrain <- edr_position(
  met,
  "terrain_tiles",
  coords = c(-0.1276, 51.5072),
  parameter_name = "Height"
)
covjson_to_tibble(terrain)

This example is also exercised by a scheduled, non-blocking live smoke check; it is never run as part of CRAN checks or the regular test suite.

Find stations

edr_locations() with no filters returns the full station list as GeoJSON. If you have sf installed, it gets promoted to an sf object automatically:

locs <- edr_locations(client, "monitoring-locations")
locs                            # sf POINTs with station attributes
plot(sf::st_geometry(locs))

Pull a time series for one station

Once you know a station ID, ask for its values. The server returns CoverageJSON; covjson_to_tibble() flattens it into one row per (coverage × parameter × timestamp):

resp <- edr_location(
  client, "daily-values",
  location_id    = "08313000",
  datetime       = "2020-01-01/2020-12-31",
  parameter_name = c("discharge", "gage_height")
)

df <- covjson_to_tibble(resp)
df
#> # A tibble: 732 × 9
#>   coverage_id parameter   parameter_label  unit  datetime                x     y     z value
#>   <chr>       <chr>       <chr>            <chr> <dttm>              <dbl> <dbl> <dbl> <dbl>
#> 1 08313000    discharge   Discharge        ft3/s 2020-01-01 00:00:00 -109.  37.0    NA   240
#> ...

Spatial filters — bbox and polygon

To grab everything inside a rectangle, use edr_cube():

cube <- edr_cube(
  client, "daily-values",
  bbox           = c(-120, 39, -118, 41),
  datetime       = "2023-01-01/2023-03-31",
  parameter_name = "discharge"
)
covjson_to_tibble(cube)

For an arbitrary polygon, edr_area() takes WKT, an sf polygon, or a matrix of (lon, lat) rows (it’ll close the ring for you):

ring <- matrix(
  c(-109, 47, -104, 47, -104, 49, -109, 49),
  ncol = 2, byrow = TRUE
)
area <- edr_area(client, "monitoring-locations", coords = ring,
                 datetime = "2022-01-01/..")
covjson_to_tibble(area)

Plot a time series

edr_plot() is a small ggplot2 wrapper over the tidy tibble:

edr_plot(resp)            # accepts an edr_response directly

Facets by parameter (so different units don’t share a y-axis) and colours by station. Add layers or themes like any other ggplot.

It also auto-detects common non-station shapes:

edr_plot(cube)            # x/y grid -> tile map
edr_plot(profile)         # varying z -> vertical profile

# or force the layout
edr_plot(profile, view = "profile")
edr_plot(cube, view = "grid")

Map stations with per-station popups

edr_map() puts the stations on a leaflet basemap. Pass data = as a named list keyed by station id (the shape [edr_explore()] produces) and each marker gets a popup with an inline plot and a “Download CSV” link for that station’s data — embedded as a data: URI so the saved HTML is selfcontained:

stations <- edr_locations(client, "monitoring-locations",
                          bbox = c(-116, 35.5, -114, 36.5))
data_list <- list("3514" = covjson_to_tibble(resp))
m <- edr_map(stations, data = data_list, popup = "plot+csv")
edr_save_html(m, "stations.html")

For a quick exploratory pass over a whole collection, edr_explore() does the fetch + plot + map in one call:

edr_explore(
  client, "daily-values",
  bbox           = c(-116, 35.5, -114, 36.5),
  datetime       = "2024-01-01/2024-03-31",
  parameter_name = "discharge",
  limit          = 25,
  file           = "snapshot.html"
)

Gridded coverages and vertical profiles can be mapped too. edr_map() detects tidy CoverageJSON grids/profiles and puts slice selectors inside the leaflet widget when there are multiple parameters or datetimes; grids also get a z selector when multiple vertical levels are present:

grid <- covjson_to_tibble(cube)
edr_map(grid)

profile <- covjson_to_tibble(profile_resp)
edr_map(profile)

edr_explore() uses the same behavior for bulk coverage queries. Use output = "plot" when you want a ggplot instead of the interactive map:

edr_explore(client, "gridded-collection",
            bbox = c(-120, 39, -118, 41),
            method = "cube")

edr_explore(client, "profile-collection",
            coords = c(-119, 40),
            method = "position")

edr_explore(client, "profile-collection",
            coords = c(-119, 40),
            method = "position", output = "plot")

Weird IDs, CSV, and an escape hatch

Some monitoring networks use compound station IDs — colon-separated triplets are a common pattern. The client URL-encodes reserved characters for you:

edr_location(client, "station-network", "1185:CO:SNTL",
             datetime = "2024-01-01/..")

If the server advertises CSV, you can ask for it instead of CovJSON:

edr_location(client, "daily-values", "08313000",
             datetime = "2010-01-01/..", format = "csv")

And if you need to hit an endpoint the package doesn’t wrap (instances, custom queryables, anything weird), edr_request() is the raw escape hatch:

edr_request(client, "collections/daily-values/instances", format = "json")

API at a glance

Function EDR endpoint
edr_client() construct a client
edr_landing() / edr_conformance() /, /conformance
edr_collections() / edr_collection() /collections
edr_queryables() /collections/{id}/queryables
edr_locations() / edr_location() /collections/{id}/locations[/{loc}]
edr_items() / edr_item() /collections/{id}/items[/{item}]
edr_position() /collections/{id}/position
edr_area() /collections/{id}/area
edr_cube() /collections/{id}/cube
edr_radius() /collections/{id}/radius
edr_trajectory() /collections/{id}/trajectory
edr_corridor() /collections/{id}/corridor
edr_request() low-level escape hatch
covjson_to_tibble() / geojson_to_sf() response parsers

What a server actually supports varies. Every query verb above is in the EDR spec and supported by the client, but most servers implement only a subset. On in-situ monitoring deployments, locations, position, cube, and area are common; radius, trajectory, and corridor less so. Hitting a verb the server doesn’t implement gives you an HTTP error. Check the data_queries column from edr_collections() before you assume a query will work.

Common parameters

Every query verb accepts the standard EDR filters:

License

MIT