EDR compatibility and supported scope

edr4r targets the JSON ecosystem around OGC API - Environmental Data Retrieval. The EDR standard permits more encodings and deployment patterns than this package promises to parse. This article makes that boundary explicit so that a server advertising an EDR conformance class is not mistaken for a guarantee that every edr4r workflow will work unchanged.

Client support

Capability Status in edr4r Notes
Landing page, conformance, collections, parameters, queryables Supported JSON discovery documents
Collection instances Supported Discovery and instance-scoped query paths
locations, items, position, area, cube, radius, trajectory, corridor Supported Read-only HTTP GET helpers; the server may implement only a subset
EDR 1.0/1.1 conformance identifiers Supported Version-tolerant capability matching; current verified endpoints advertise 1.0 identifiers
EDR 1.1 multi-geometries and custom dimensions Supported subset WKT MULTIPOINT, MULTIPOLYGON, and MULTILINESTRING; custom extent metadata is retained and named dimension parameters pass through ...
CoverageJSON Supported subset Inline Coverage/CoverageCollection with primitive, regular, or tuple axes and inline NdArray ranges; nonstandard coordinates are retained as .axis_* columns
GeoJSON Supported Converted to sf when installed; otherwise retained as an edr_response
CSV Limited Parsed by the low-level client and supported directly by location queries
NetCDF, GeoTIFF, GRIB and other native encodings Download only Use edr_request(..., parse = FALSE) with the server’s advertised f value; no native R conversion yet
External CoverageJSON domains/ranges and TiledNdArray Not supported Rejected explicitly rather than partially or silently parsed
Pagination/link following Supported subset Opt-in for GeoJSON FeatureCollections from edr_locations() / edr_items(); bounded by finite page and feature caps
Multi-location retrieval Supported subset Explicit IDs through sequential edr_location_batch(); optional bounded calendar windows, custom-axis-aware exact deduplication, checkpoint/resume, CoverageJSON/CSV, and a finite expanded-plan cap
HTTP 202/308 asynchronous polling Not yet supported HTTP 204 empty responses are supported
POST queries and EDR Part 2 Pub/Sub Out of current scope The package is a synchronous, read-only client

CRS values are passed to the server as requested. edr4r retains effective CoverageJSON referencing metadata and does not silently reproject coverage coordinates. A projected grid remains valid in edr_plot(), which uses source coordinates. edr_map() requires WGS 84/CRS84 longitude/latitude: it rejects a declared projected or other known geographic CRS and out-of-range coordinates or inferred cell bounds. Missing or custom references produce a warning only after those range checks pass. Station sf geometry is transformed to WGS 84 for Leaflet display after any ID or source-CRS spatial matching has been completed; missing sf CRS metadata warns before plausible degree-range coordinates are used as-is.

Current pygeoapi endpoints commonly serve GeoJSON and CoverageJSON through f=json, which remains the compatibility default. When a stricter endpoint advertises an encoding-specific token, pass it explicitly while keeping the desired parser, for example edr_position(..., format = "covjson", f = "CoverageJSON") or edr_request(..., query = list(f = "CoverageJSON"), format = "covjson").

Return and failure contract

Discovery tables (edr_collections(), edr_parameters(), and edr_instances()) return tibbles with stable scalar columns and list columns for metadata that cannot be flattened without loss. Raw collection/instance documents remain available in a raw list column. Detailed discovery calls return the parsed JSON list.

CoverageJSON query verbs return an edr_response; convert it explicitly with covjson_to_tibble(). GeoJSON collection verbs return sf when sf is installed and otherwise retain an edr_response. CSV responses return a tibble. HTTP 204 produces a typed empty result rather than a parser failure. The first nine CoverageJSON columns remain coverage_id, parameter, parameter_label, unit, datetime, x, y, z, and value. Nonstandard coordinates follow as .axis_<coordinate> columns without changing row cardinality. A versioned edr_covjson_metadata attribute retains per-coverage domain type, axis details, and effective referencing, including collection-level values inherited by child coverages. Package batch, explore, and checkpoint paths preserve that attribute; arbitrary third-party row binds are not promised to do so. For numeric CoverageJSON ranges, values serialized as JSON strings are coerced only when every non-missing string is finite and safely representable; actual text, overflow/underflow, and unsafe declared integers remain explicit parser errors.

edr_location_batch() returns an edr_location_batch object with three stable tibbles: requests (one status row per planned location/window query), data (row-bound CoverageJSON/CSV data with .request_id and .location_id provenance), and errors (preserved conditions when on_error = "collect"). With explicit include_parameters = TRUE, a fourth parameters tibble holds the nonduplicated collection or instance parameter catalog; otherwise that component is NULL and no metadata request is made. Optional day/week/month/year chunks require a finite closed interval. Adjacent windows share their boundary; exact rows repeated across windows for the same location are removed by default, while requests$n_rows retains raw response counts. Timestamp bounds are normalized to UTC before calendar arithmetic and may contain up to six fractional-second digits; values that R cannot represent without loss are rejected before requests begin. It is sequential in this release so the normal client retry policy remains authoritative.

Checkpointing is opt-in and preserves only terminal successful or empty parsed responses. An interrupted or failed request has no terminal file and is retried on the next explicit resume = TRUE call. Each checkpoint is locked while in use and fingerprinted against the effective endpoint, ordered location/window plan, format, and query arguments. A mismatch or corrupt result aborts before network activity. Headers, full query URLs, and error conditions are not stored; the parsed observation data is stored and should be protected accordingly. Checkpointed clients require an absolute HTTP(S) base URL without an embedded query, fragment, username, or password. Rotating credentials belong in client headers; the caller remains responsible for resuming under the same logical authorization context.

The checkpoint schema is versioned with parser semantics. Schema 2 records the custom-axis identity and CoverageJSON metadata contract; earlier checkpoints are rejected explicitly and must be recreated rather than combined with newly parsed rows.

Parameter catalogs are not checkpointed or included in the request-plan fingerprint. A resumed batch with include_parameters = TRUE obtains current metadata through the client’s discovery cache even when every data request is restored. max_requests counts data requests only, and on_error does not collect a parameter-discovery failure.

Pagination is deliberately opt-in. The client follows only the server-advertised body link with rel = "next", treats its cursor/offset as opaque, and requires the continuation to remain on the original request’s service origin. Reaching max_pages or max_features while another page exists is a typed error, not a silently partial result. edr_explore() does not enable collection-wide pagination automatically.

Local argument and parser failures are raised before issuing unsafe follow-up requests. HTTP failures retain their httr2_http_* status classes and include a useful server error body when one is available. edr_diagnose() is the exception: after validating its arguments, it records metadata/network failures as fail, warn, or skip rows so one broken discovery endpoint does not hide the rest of the report.

Inspect before querying

The 0.2 discovery layer retains query-specific formats, units, CRS details, extents, and parameter metadata instead of flattening them away.

client <- edr_client("https://labs.metoffice.gov.uk/edr")

service <- edr_capabilities(client)
terrain <- edr_capabilities(client, "terrain_tiles")

edr_supports(client, "terrain_tiles", query = "position")
edr_supports(
  client, "terrain_tiles",
  query = "position", format = "CoverageJSON"
)

# Fresh, metadata-only probes; no data query is issued.
edr_diagnose(client, "terrain_tiles")

Discovery responses are cached per client for a bounded period. Use refresh = TRUE on a discovery call when current server state matters, or clear that client’s metadata with edr_cache_clear(client).

Verified endpoints

The table records direct compatibility checks, not a permanent availability promise. Server metadata and pagination behavior were last inspected on 2026-07-10.

Endpoint Role Advertised query types observed edr4r coverage
USGS waterdata Operational U.S. streamgage service locations Collection/location discovery, opaque cursor pagination, latest-record station/batch time series, CoverageJSON parsing, per-location exploration; location queries currently ignore datetime
Western Water Datahub Operational multi-network pygeoapi deployment locations, items, position, area, cube Discovery, parameters, offset pagination for items, station and bulk queries, plotting and mapping
Met Office Labs Non-operational technical demonstrator locations, items, instances, position, area, cube, radius, trajectory Cross-implementation metadata, instances, query-specific formats, terrain point coverage, and an explicit-CRS population grid

The Met Office service describes itself as an example rather than an operational API. It is useful for interoperability testing because its metadata exercises instances and richer query variables, but production workflows must not depend on its availability.

Frozen and live checks

Tests use small, reviewed fixtures so CRAN and pull-request checks are deterministic and offline. Met Office fixtures retain the response shapes needed for capability, instance, and single-Coverage parsing tests while omitting large payloads and verbose WKT definitions.

CoverageJSON axis bounds are not yet used as cell edges. Grid visualization infers cell bounds from coordinate centers, including range validation of the derived edges; external domains/ranges and tiled arrays remain unsupported as listed above.

Frozen cursor- and offset-shaped pages exercise both pagination styles without relying on live service state. Security and edge-case tests also cover relative links, cycles, malformed/multiple next links, cross-origin continuations, and page or feature caps.

A separate scheduled/manual workflow performs tiny, non-blocking live probes: bounded USGS cursor pagination and station batching, bounded WWDH offset pagination, plus the Met Office capability, terrain, and population-grid checks. An endpoint outage does not fail package CI; it is evidence of availability or interoperability drift to investigate.

What “supports EDR” means here

edr4r can construct every EDR Part 1 query path listed above, but a particular server may not advertise or implement each path or encoding. Use capability discovery rather than treating the existence of an R function as a server guarantee. For an unfamiliar endpoint, start with edr_diagnose(), inspect collection capabilities, and then issue the smallest representative query.