--- title: "Tutorial: USBR RISE data through the Western Water Datahub EDR API" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Tutorial: USBR RISE data through the Western Water Datahub EDR API} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- This is an end-to-end tutorial on finding, retrieving, downloading, and reusing Bureau of Reclamation RISE data through the [Western Water Datahub](https://api.wwdh.internetofwater.app) (WWDH) EDR interface. It is written for USBR staff who are comfortable with computers but are not web developers. You do not need to install anything or write any code. Every step works by opening a URL in a normal web browser. Where a command-line alternative is useful, a `curl` version is shown too, but it is always optional. Key example URLs in this tutorial were checked against the live service on June 10, 2026. The Datahub evolves, so if an example ever surprises you, start again from the discovery steps in sections 3 and 4 — those are the durable part. ## 1. The ideas you need (and none you don't) **RISE** ([data.usbr.gov](https://data.usbr.gov/)) is Reclamation's public data catalog: reservoir storage, elevations, releases, canal flows, weather observations, and more, organized by *location* and *parameter*. **EDR** (OGC API - Environmental Data Retrieval) is an open standard for asking environmental data questions with plain URLs: "what do you have?", "what stations are in this box?", "give me parameter X at location Y between these dates." **WWDH** is a server that republishes RISE (and several other federal water datasets) through that standard. The collection you will use is called `rise-edr`. Why go through WWDH instead of RISE directly? Two reasons: 1. The URL patterns are a standard. Once you can query `rise-edr`, you can query SNOTEL snowpack, USACE reservoir, and PRISM climate collections on the same server with the *same* URL grammar (section 10). 2. Every response is available in machine-friendly formats (JSON, CSV) *and* a human-readable HTML page, so you can browse first and automate later. ### Anatomy of a request URL Every request in this tutorial is just a URL with this shape: ``` text https://api.wwdh.internetofwater.app / collections/rise-edr/locations/3514 ? f=json & parameter-name=3 └──────────────── base ─────────────┘ └───────────── path ──────────────┘ └───────── query ────────┘ ``` - The **base** is always `https://api.wwdh.internetofwater.app`. - The **path** says what kind of thing you want (a list of locations, data at one location, a data cube over an area, ...). - The **query** (everything after `?`, with pieces separated by `&`) refines the request: format, dates, parameters, bounding box. ### The format switch: `f=` Almost every URL accepts an `f` parameter that picks the response format. This is the single most useful thing to remember: | `f=` | What you get | When to use it | |---|---|---| | `html` | A readable web page, often with a map or table | Browsing, orienting yourself, sharing a link with a colleague | | `json` | Structured data (GeoJSON or CoverageJSON) | Downloads, scripts, GIS, Excel Power Query | | `csv` | A spreadsheet-ready table (location/feature lists) | Direct download into Excel | If you leave `f=` off, your browser usually gets the HTML page and tools like `curl` usually get JSON. Being explicit avoids surprises. The HTML pages are genuinely useful — the locations page renders a map, and the items page renders a sortable table — but they are a *window* onto the data, not the data itself. When you want to keep or analyze the numbers, switch the same URL to `f=json` or `f=csv`. ### If you use curl Always wrap the URL in single quotes, because `?` and `&` mean something to the shell: ``` sh curl -sS 'https://api.wwdh.internetofwater.app/collections/rise-edr?f=json' ``` Add `-o filename` to save the response to a file. That is all the curl you need for this tutorial. ## 2. Five-minute orientation in the browser Open these three pages in order, just to look around: 1. The landing page: 2. The list of all collections on the server: 3. The RISE collection page: The RISE collection page shows the description ("a proxy of the RISE API into the OGC API EDR format"), the spatial extent, the list of parameters, and links to the four query types RISE supports. For the complete technical reference of every route and parameter, see the [Swagger/OpenAPI page](https://api.wwdh.internetofwater.app/openapi). Here is the live collection page, embedded from the service (this and the other embedded views below require an internet connection; [open in its own tab](https://api.wwdh.internetofwater.app/collections/rise-edr)): ## 3. Discover what RISE offers The machine-readable version of the collection page is the anchor for everything else: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr?f=json ``` Two blocks in that document matter most. **`data_queries`** lists the query types this collection supports. As of June 10, 2026, `rise-edr` advertises four: | Query type | URL path | Question it answers | |---|---|---| | `locations` | `/collections/rise-edr/locations` | Where are the monitoring locations? What data does one location have? | | `items` | `/collections/rise-edr/items` | Feature catalog: searchable, filterable list of RISE locations with their attributes | | `cube` | `/collections/rise-edr/cube` | Give me data for everything inside this bounding box | | `area` | `/collections/rise-edr/area` | Give me data for everything inside this polygon | Do not assume other EDR routes (like `/position` or `/radius`) work for RISE — a route that is not advertised in `data_queries` will return an error. Other collections on the server advertise different sets. **`parameter_names`** lists every data variable you can request, keyed by RISE's numeric parameter ids. Each entry carries a human-readable name, description, and unit. ## 4. Find the parameter ids you need RISE parameters are identified by *numbers*, not names. The URL takes the number; the name and unit are there so humans can pick the right number. A few examples from the live metadata: | Parameter id | Name | Unit | |---|---|---| | `3` | Lake/Reservoir Storage (daily) | af | | `1830` | Lake/Reservoir Release - Total | cfs | | `1834` | Lake/Reservoir Elevation | ft | | `1811` | Precipitation | in | | `1777` | Water Temperature | °F | There are hundreds more. Two ways to find the one you need: **In the browser:** open and use your browser's find-in-page (Ctrl+F / Cmd+F) on the parameter list. **With curl and jq:** pull just the id/name/unit triples: ``` sh curl -sS 'https://api.wwdh.internetofwater.app/collections/rise-edr?f=json' | jq '.parameter_names | to_entries[] | {id: .key, name: .value.name, unit: .value.unit.symbol.value}' ``` Note that the same concept (for example "Lake/Reservoir Storage") may appear under several ids that differ by timestep or computation method. Read the description text before committing to an id, and remember: **labels are for humans; ids go in URLs.** ## 5. Find locations You will usually start from a place, not a parameter: "what does RISE monitor near here?" There are two complementary tools. ### 5a. The locations map Open the locations page in a browser and you get a map of RISE locations: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/locations ``` Constrain it with a bounding box. A `bbox` is four comma-separated numbers — **longitude and latitude, in this order**: ``` text min_lon,min_lat,max_lon,max_lat ``` (Remember that longitudes in the western U.S. are negative, and the *minimum* longitude is the *westernmost* edge.) For example, the area around the lower Colorado River near Lake Mead and Lake Havasu: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/locations?bbox=-115,33,-113,37 ``` The live map ([open in its own tab](https://api.wwdh.internetofwater.app/collections/rise-edr/locations)): Switch the same URL to `f=json` to get the locations as a GeoJSON `FeatureCollection` — each feature has an `id` (the RISE location id you will use in section 6), a point geometry, and properties such as `locationName`, `locationTypeName`, `elevation`, `timezone`, and `projectNames`. ### 5b. The items catalog: filter, browse, download The `/items` route serves the same locations as a *searchable feature catalog*. In a browser it renders as a table with one row per location: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/items?f=html&bbox=-115,33,-111,35&locationTypeName=Lake%2FReservoir&limit=20 ``` That request — checked live on June 10, 2026 — returns seven lake/reservoir locations in a Lower Colorado bounding box, including `3515` (Lake Havasu Parker Dam and Powerplant) and several Apache Lake sites. The live view ([open in its own tab](https://api.wwdh.internetofwater.app/collections/rise-edr/items?f=html&bbox=-115,33,-111,35&locationTypeName=Lake%2FReservoir&limit=20)): Three things are happening in that URL: - `bbox=-115,33,-111,35` limits the spatial window. - `locationTypeName=Lake%2FReservoir` filters on a feature property. The `%2F` is a URL-encoded `/` (the value is literally `Lake/Reservoir`; a raw slash would confuse the URL). - `limit=20` caps the number of rows. Start generous: some filters are applied *after* the limit, so a small limit can hide matches that a larger one finds. Which property names can you filter on? Ask the queryables route: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/queryables?f=json ``` Any property listed there (such as `locationTypeName` or `projectNames`) can be used as a query parameter on `/items`. **Download the catalog as a spreadsheet:** change `f=html` to `f=csv` and the same filtered table downloads as a CSV file that opens directly in Excel — including useful engineering attributes like Total Capacity, Active Capacity, and vertical datum notes where RISE has them: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/items?f=csv&bbox=-115,33,-111,35&locationTypeName=Lake%2FReservoir&limit=20 ``` **Inspect one location:** every row links to a single-feature page, e.g. Lake Mead is location `3514`: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/items/3514 ``` ## 6. Get data for one location Once you know a location id and a parameter id, the data request is one URL: `/locations/{locationId}` plus a date range and parameter. Lake Mead (`3514`), daily storage (`3`), January 2023: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31¶meter-name=3 ``` Or as curl, saving to a file: ``` sh curl -sS -o lake-mead-jan2023.json \ 'https://api.wwdh.internetofwater.app/collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31¶meter-name=3' ``` The two query parameters: - `datetime=` takes an ISO date, or a range with a slash: `2023-01-01/2023-01-31`. Some providers accept an open end: `2023-01-01/..`. Without `datetime`, you may get the full period of record — fine for some series, enormous for others. Start narrow. - `parameter-name=` takes one or more parameter ids, comma-separated: `parameter-name=3,1834`. ### Reading the response (CoverageJSON) Data queries return **CoverageJSON**, a compact format that keeps the time axis and the values in parallel arrays: ``` json { "type": "Coverage", "domain": { "domainType": "PointSeries", "axes": { "x": {"values": [-114.7]}, "y": {"values": [36.0]}, "t": {"values": ["2023-01-01T07:00:00Z", "2023-01-02T07:00:00Z"]} } }, "ranges": { "3": { "type": "NdArray", "axisNames": ["t"], "values": [8730345, 8732112] } } } ``` To turn this into a table, pair them up positionally: the first time in `domain.axes.t.values` goes with the first number in `ranges.{parameter}.values`, the second with the second, and so on. `axisNames` tells you which axis(es) the values run along — for a single station it is just `["t"]`. | t | value of parameter 3 (af) | |---|---| | 2023-01-01T07:00:00Z | 8730345 | | 2023-01-02T07:00:00Z | 8732112 | A note on CSV here: the server advertises `f=csv` on data queries, but in testing the EDR data routes returned empty CSV bodies. For time series, treat `f=json` as the dependable format and convert it yourself (section 9), or use the `/items` CSV for catalog tables. ## 7. Get data for many locations at once: `/cube` When you want every matching series inside a bounding box, use the `cube` route instead of looping over locations. All RISE storage (`3`) series in a small box around Lake Havasu, for calendar 2024: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/cube?f=json&bbox=-114.3,34.1,-113.5,34.4&datetime=2024-01-01/2024-12-31¶meter-name=3 ``` This returns a `CoverageCollection`: a `coverages` array in which each entry is one location's series, with its own `domain` (the x/y coordinates and time axis) and `ranges` (the values). The example above returns one coverage — Lake Havasu, 365 daily storage values. Practical guidance for `cube`: - Keep the bbox tight and the date range short on the first try, then widen one thing at a time. Each cube request fans out to the upstream RISE API, so big requests are slow and can time out. - If a broad cube fails, fall back to the discovery pattern: find candidate ids with `/items` (section 5b), then request each one via `/locations/{id}` (section 6). ## 8. Get data inside a polygon: `/area` If your area of interest is a watershed or management boundary rather than a box, the `area` route accepts a polygon in WKT (well-known text) via `coords=`. A readable WKT polygon: ``` text POLYGON((-115 35,-113 35,-113 37,-115 37,-115 35)) ``` URLs cannot contain spaces, so each space becomes `%20`: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/area?f=json&coords=POLYGON((-115%2035,-113%2035,-113%2037,-115%2037,-115%2035))&datetime=2024-01-01/2024-01-31¶meter-name=3 ``` Two rules save most of the grief here: the polygon ring must close (first coordinate repeated last), and every space must be encoded. For complex boundaries, simplify the polygon first or use a tool that encodes URLs for you — very long URLs can exceed browser and server limits. If a box is close enough, prefer `cube`; it is simpler and faster. ## 9. Downloading and using the data You now know how to build the URLs. Getting the results into your tools: **Browser:** open the `f=json` URL and use Save Page As (Ctrl+S / Cmd+S), or right-click a link and Save Link As. The `/items?f=csv` URLs download directly as spreadsheet files. **curl:** `curl -sS -o output.json ''`. **Excel:** open `/items` CSV files directly. For JSON time series, Data → Get Data → From Web in Excel's Power Query accepts the `f=json` URL and can unpivot `domain.axes.t.values` against `ranges.{id}.values`, though this takes some Power Query comfort — many people find it easier to ask a script-writing colleague (or an AI assistant) for a five-line converter from CoverageJSON to CSV. **GIS:** QGIS and ArcGIS read GeoJSON natively. In QGIS, Layer → Add Layer → Add Vector Layer, choose Protocol: HTTP, and paste a `/locations?f=json` or `/items?f=json` URL to map RISE locations directly — no download step needed. **R:** this package (`edr4r`) wraps all of these routes; `edr_locations()`, `edr_cube()`, and `covjson_to_tibble()` reproduce this tutorial in a few lines. See the companion vignette, *Western Water Datahub collection field guide*. **Python:** `requests` plus the `covjson-pydantic` or plain `json` module; the axis/range pairing logic in section 6 is a five-line loop. ## 10. Interoperating beyond RISE Everything above transfers. The same server hosts sibling collections that answer to identical URL grammar — only the collection id, the parameter ids, and the advertised query types change: | Collection id | Source | Example use | |---|---|---| | `snotel-edr` | USDA NRCS SNOTEL | Snow water equivalent above your reservoir | | `awdb-forecasts-edr` | USDA AWDB | Seasonal water supply forecasts | | `usace-edr` | USACE Access2Water | Corps reservoir storage and outflow | | `usgs-prism` | PRISM | Monthly precipitation and temperature grids | Check each collection's `data_queries` before assuming a route exists (for example, `usgs-prism` supports `position` and `cube`, not `locations`). ### Translating concepts across agencies: `parameterGroups` Parameter ids differ across agencies: RISE storage is `3`, USACE storage is `Flood Storage` or `Conservation Storage`. WWDH publishes a crosswalk in the top-level collections document: ``` text https://api.wwdh.internetofwater.app/collections?f=json ``` Its `parameterGroups` array maps a shared concept to each collection's ids. As checked on June 4, 2026, the `Lake/Reservoir Storage` group included: ``` json { "name": "Lake/Reservoir Storage", "members": { "rise-edr": ["1470", "56", "51", "47", "3"], "usace-edr": ["Flood Storage", "Conservation Storage", "Percent Flood Pool", "Percent Conservation Pool"] } } ``` So a cross-agency comparison of 2024 storage near Parker Dam is two parallel cube requests over the same bbox: ``` text https://api.wwdh.internetofwater.app/collections/rise-edr/cube?f=json&bbox=-114.3,34.1,-113.5,34.4&datetime=2024-01-01/2024-12-31¶meter-name=3 https://api.wwdh.internetofwater.app/collections/usace-edr/cube?f=json&bbox=-114.3,34.1,-113.5,34.4&datetime=2024-01-01/2024-12-31¶meter-name=Flood%20Storage ``` (The `%20` encodes the space in `Flood Storage` — USACE uses worded ids.) When verified, the first returned Lake Havasu's 365 daily RISE storage values and the second returned Alamo Dam's Corps storage record for the same year. Expect timestep differences across agencies: RISE returned daily values; the USACE series was irregular. Aligning them is an analysis step, not an API option. ## 11. The whole workflow on one page ``` text 1. ORIENT /collections/rise-edr (browser, f=html) 2. PARAMETERS /collections/rise-edr?f=json -> parameter_names: pick exact ids 3. LOCATIONS /collections/rise-edr/items?f=html&bbox=...&{queryable}=...&limit=... -> browse table; switch to f=csv to download; note location ids 4. ONE SERIES /collections/rise-edr/locations/{id}?f=json&datetime=...¶meter-name=... 5. MANY SERIES /collections/rise-edr/cube?f=json&bbox=...&datetime=...¶meter-name=... 6. CROSSWALK /collections?f=json -> parameterGroups, then repeat 3-5 on the sibling collection ``` Start small — one location, one parameter, one month. Widen one dimension at a time once the small request works. ## 12. Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | 404 on a query route | Route not advertised for this collection | Re-check `data_queries` in `/collections/rise-edr?f=json` | | 400 on `/area` | WKT not URL-encoded or ring not closed | Encode spaces as `%20`; repeat the first coordinate at the end | | 204 / empty body | Valid request, but nothing matched | Try a different parameter id, location, or a shorter date range | | Empty CSV from a data query | CSV not populated for EDR data routes | Use `f=json` for time series; `f=csv` works on `/items` | | 500 or very slow on `/cube` | Upstream RISE could not satisfy a broad query | Shrink bbox, shorten dates, request one parameter | | Empty `features` on `/items` | Filter too narrow, or `limit` too small | Raise `limit`, drop filters one at a time, check the `f=html` view | | Wrong or empty data for a parameter | Parameter id not collected at that location | Open the location in the items table; try a sibling id from section 4 | | Shell error before any response | URL not quoted in the terminal | Wrap the whole URL in single quotes | | Numbers but no meaning | Units unknown | Look up the id under `parameter_names` — units are in the metadata | If you get stuck on what a route accepts, the [OpenAPI page](https://api.wwdh.internetofwater.app/openapi) documents every route and query parameter interactively, and every JSON document on the server contains a `links` section pointing to its related pages — when in doubt, follow the links.