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:
tibble (one row
per coverage × parameter × time step), via
covjson_to_tibble().sf object, via
geojson_to_sf().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).
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.
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))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
#> ...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)edr_plot() is a small ggplot2 wrapper over
the tidy tibble:
edr_plot(resp) # accepts an edr_response directlyFacets 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")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")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")| 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, andareaare common;radius,trajectory, andcorridorless so. Hitting a verb the server doesn’t implement gives you an HTTP error. Check thedata_queriescolumn fromedr_collections()before you assume a query will work.
Every query verb accepts the standard EDR filters:
datetime — an ISO-8601 instant or interval. Accepts
"2020-01-01/2020-12-31", an open interval
"2020-01-01/..", or a length-2 character vector
c("2020-01-01", "2020-12-31").parameter_name — a character vector of parameter names;
sent as a comma-separated parameter-name= query. Use
edr_parameters() to discover valid names.bbox — numeric length-4
(minx, miny, maxx, maxy) or length-6 (with z).coords — for
position/area/radius/trajectory/corridor:
a WKT string, a numeric vector / 2-column matrix of lon-lat, or an
sf/sfc geometry.z, crs, limit — passed
through when supplied.... — any extra query parameter is forwarded
verbatim.MIT