---
title: "EDR compatibility and supported scope"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{EDR compatibility and supported scope}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

`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.

```r
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](https://api.waterdata.usgs.gov/ogcapi/beta/) | 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](https://api.wwdh.internetofwater.app) | 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](https://labs.metoffice.gov.uk/edr/collections?f=html) | 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.
