Aller au contenu

omnilab/usgs-water

Omnilab USGS Water: the USGS's water data through the Water Data OGC APIs (which replace the WaterServices) - a monitoring location's series (discharge, gage height, water temperature, any parameter by its USGS code) as recorded every 5 or 15 minutes, hourly, or as the USGS's daily statistics, at a station named or the nearest one of a point; a monitoring location as an entity with its place, drainage area and the series it keeps; the locations by name. No key needed; an api.data.gov key raises the rate limit.

Étage
Fournisseur · Omnilab
Version
1.x
Suite de conformité
non branchée
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnilab/usgs-water:1.x-dev

The USGS's Water Data OGC APIs (https://api.waterdata.usgs.gov/ogcapi/v1/) for omnisense/omnilab: a monitoring location's discharge, gage height, water temperature or any parameter, as recorded, hourly or daily, at a usgs.site or the nearest location of a point, as series; the location itself as an entity; locations by name. No key needed.

Installation

composer require omnilab/usgs-water
use Omnilab\UsgsWater\UsgsWaterSourceFactory;

$water = (new UsgsWaterSourceFactory($httpClient))->create([
    'mailto' => 'support@glitchr.io',  // optional
    'api_key' => null,                 // optional: an api.data.gov key for the USGS's water APIs
    'radius_km' => 25,                 // optional: how far from a Point the nearest location is looked for
]);

With the Symfony bundle, the factory is registered as usgs-water when the bundle knows it; otherwise register UsgsWaterSourceFactory as a service.

Credentials

None needed. The APIs' rate limits count per key or per address; an api.data.gov key for the USGS's water APIs (from the USGS's signup page) raises them: give it as api_key, sent as X-Api-Key.

Options

The factory's options, with the defaults it applies (UsgsWaterSourceFactory):

Option Type Default Required Effect
api_key ?string null no an api.data.gov key, sent as X-Api-Key: a higher rate limit
radius_km float 25 no how far from a Point the nearest monitoring location keeping the parameter is looked for, in kilometres
mailto ?string null no a contact address, sent in the User-Agent
user_agent ?string null no the whole User-Agent, in place of the one made from mailto
throttle float 0 no seconds left between two calls: the limits count per key or per address
base_uri string https://api.waterdata.usgs.gov/ogcapi/v1/ no the API the source calls

Capabilities

capabilities() answers entity, search, series, historical, station; an operation it does not answer throws NotSupportedException before any call.

Capability Supported
entity yes a monitoring location (USGS-01646500, usgs.site:01646500, its page, its API record), with the series it keeps; another scheme refused before any call
search yes the USGS's locations whose name holds the text (CQL2 LIKE), paged by the API's cursor; no total
series yes discharge, gage_height, water_temperature... or any USGS parameter code of five digits, at a usgs.site or at the nearest location of a Point, as recorded, hourly or daily (mean, max, min, sum: the API's statistics); no location near: a series without points per variable, at the point; an unknown location: an empty list
products no
occurrences no
datasets no
jobs no
open no
structure no
cross_references no none: the APIs give no other base's id (Wikidata carries the location's number, P13020)
forecast no
historical yes the records of the past (provisional when recent)
area no a box is refused before any call
station yes usgs.site
cloud_cover no

Which API

The Water Data OGC APIs (https://api.waterdata.usgs.gov/ogcapi/v1/, v1 the current version), not the WaterServices (waterservices.usgs.gov/nwis/iv): on 2026-10-15 the WaterServices still answer, but their own page says they will be "permanently shut down on February 22nd, 2027", slowed and interrupted on purpose from January 2027, and that "users must migrate to https://api.waterdata.usgs.gov". The collections read:

Collection For
monitoring-locations/items/{id} a location (entity()), its place and name for a series
monitoring-locations/items?filter= search(): CQL2 text, agency_code = 'USGS' AND monitoring_location_name LIKE '%...%', paged by the API's cursor
time-series-metadata/items the series a location keeps (entity()); the locations of a box keeping a parameter (a Point's nearest)
continuous/items the values as recorded ("continuous", every 5 or 15 minutes)
daily/items the daily values, by statistic (00003 mean, 00001 max, 00002 min, 00006 sum)

Every call asks f=json; the series name only the properties read (properties=time,value, parameter_code,unit_of_measure, skipGeometry=true), at most 50,000 a page (a larger limit is a 400), the next page by the next link, fifty pages at most for one query. The API does not promise an order: the points are sorted.

Its shapes

A monitoring location is an entity (and a station of the core's usgs.site scheme); what it measures is a series (Capability::STATION: a station named answers its own records). A Point answers the series of the nearest location within radius_km that keeps the first variable asked over the period (as recorded, or daily for SeriesQuery::DAILY); a BBox is refused. search() finds the USGS's locations by name.

Calls

Method Reads Calls
entity() USGS-01646500, usgs.site:01646500, its page on waterdata.usgs.gov, its record in the API two: the location, its series' metadata
search() a text in the name (upper-cased: the names are), limit (10,000 at most), the cursor one a page
series() the variables below or any five-digit USGS parameter code; a usgs.site station or a Point; the period (seven days back by default; a date alone is the whole day, in the query's time zone, UTC by default); the step and the aggregation the location (or, for a Point, the box's series and the nearest's name), then one call for every variable, a page per 50,000 values
Variable Parameter code
discharge 00060 (ft^3/s)
gage_height 00065 (ft)
water_temperature 00010 (degC)
specific_conductance 00095 (uS/cm, at 25 °C)
dissolved_oxygen 00300 (mg/l)
ph 00400
turbidity 63680 (_FNU)
precipitation 00045
groundwater_level 72019 (depth to water below land surface)

The units in parentheses are those the API gave Little Falls' series on 2026-10-15. A series' unit is the API's unit_of_measure, as it writes it (ft^3/s, ft, degC). The step:

  • SeriesQuery::DAILY: the USGS's daily values, the mean unless the query aggregates otherwise (Series::MAX, MIN, SUM: the USGS's own daily statistics, computed in the location's local days; a day is given at its midnight in the query's zone);
  • any other step (HOURLY, the default; PT15M, PT5M...): the values as recorded on the step's grid from the start of the period - without an aggregation the value recorded at each step's start (null when none was), with one the step's MAX, MIN, MEAN or SUM.

A variable it does not read, another aggregation, a box or another scheme's station are refused before any call. An unknown location is null (404); a Point with no location near: a series without points per variable, at the point. A 429 is a RateLimitedException; a 400 (InvalidParameterValue) a ProviderException with the API's description.

What an entity holds

id (usgs.site:USGS-01646500), label (its name), domain earth, kind monitoring location, place (its latitude and longitude; its altitude in metres, converted from feet), url (its page on waterdata.usgs.gov), and:

Key API Value
site_type, agency, state, county, hydrologic_unit, time_zone site_type, agency_name, state_name, county_name, hydrologic_unit_code, time_zone_abbreviation text
drainage_area, contributing_drainage_area drainage_area, contributing_drainage_area Quantity, mi²
altitude, vertical_datum altitude ± altitude_accuracy, vertical_datum Quantity, ft; text (NAVD88)
well_depth well_constructed_depth Quantity, ft (below land surface)
variable each time series' metadata 00060 Discharge (as recorded, ft^3/s): 1972-06-09/2026-10-15, one property each

A location of a search holds the same but the series it keeps (entity() reads them).

The Collector

The APIs give no id of another base. Into USGS Water: omnilab/wikidata reads an item's USGS monitoring location number (P13020) into the usgs.site scheme, so the Collector follows a Wikidata item into this source - and offers a location's id to Wikidata, which looks items up by it.

What the APIs do not give here

The field measurements, the peaks, the water-quality samples (the Water Quality API), the statistics over years, the time series' revisions and methods, the reference lists (parameter codes, site types...), other agencies' locations (USEPA-...), the CQL2 filters beyond the name and a bounding box, and the WaterServices are not read.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnilab\Model\Series;
use Omnilab\Source\Query;
use Omnilab\Source\SeriesQuery;
use Omnilab\UsgsWater\UsgsWaterSourceFactory;
use Symfony\Component\HttpClient\HttpClient;

$water = (new UsgsWaterSourceFactory(HttpClient::create()))->create(['mailto' => 'you@example.org', 'api_key' => getenv('USGS_WATER_API_KEY') ?: null]);

$potomac = $water->entity('USGS-01646500');
echo $potomac->label, ' (', $potomac->id, '): ', $potomac->place, ', drainage ', $potomac->property('drainage_area'), ', ', count($potomac->properties('variable')), " series kept\n";

[$discharge] = $water->series(new SeriesQuery(['discharge'], $potomac->id, new DateTimeImmutable('2026-10-20'), new DateTimeImmutable('2026-10-26'), SeriesQuery::DAILY, Series::MEAN));
printf("%s (%s), daily means: %s\n", $discharge->label, $discharge->unit, implode(', ', $discharge->values()));

$page = $water->search(new Query('potomac river', limit: 3));
printf("%s locations named \"potomac river\"; the first %d: %s\n", $page->total ?? '?', count($page), implode('; ', array_map(static fn ($l) => $l->label, $page->items)));
POTOMAC RIVER NEAR WASH, DC LITTLE FALLS PUMP STA (usgs.site:USGS-01646500): 38.949777777778,-77.127638888889, drainage 11560 mi², 65 series kept
POTOMAC RIVER NEAR WASH, DC LITTLE FALLS PUMP STA (USGS-01646500) (ft^3/s), daily means: 5480, 4820, 4260, 3790, 3500, 3230, 
? locations named "potomac river"; the first 3: NORTH BRANCH POTOMAC RIVER NEAR KEMPTON, MD; NORTH BRANCH POTOMAC RIVER NEAR WILSON, MD; NORTH BRANCH POTOMAC RIVER AT WILSON, MD

(as answered on 2026-10-27, without a key; the 26th's daily mean was not published yet: a null, the last value of the list)

# config/packages/omnilab.yaml
omnilab:
    sources:
        water: { factory: usgs-water, options: { mailto: '%env(OMNILAB_MAILTO)%', api_key: '%env(default::USGS_WATER_API_KEY)%' } }

Errors

Every call goes through HttpSource: a 404 or a 410 (and a 204) is null - an empty page for a list -; a 429 is a RateLimitedException (retryAfter from Retry-After); a 5xx, a time-out or a host that does not answer an UnavailableException; any other 4xx a ProviderException with its status and what the service said; an answer that is not JSON a ProviderException, a web page answered for JSON an UnavailableException.

Before any call (NotSupportedException): a variable that is neither served nor a parameter code of five digits, an aggregation the daily statistics do not have, a box, a station of another scheme. A series that would take more than fifty pages of 50,000 values is a ProviderException ("narrow the period"). An unknown location is the API's 404, null.

Verified, and not

Against the real APIs on 2026-10-15: the Potomac River near Washington at Little Falls by its id, its number and its page (11,560 mi², 37.04 ± 0.17 ft NAVD88, 65 series kept, its daily discharge since 1930-03-01); an unknown location (404, null); its discharge and gage height on 2026-10-14 hourly in one call (14,000 to 24,300 ft³/s, 4.38 to 5.26 ft), every five minutes (288 values), the hours' maxima; its daily mean discharge from 2026-10-01 to 10-07 (2,650 to 1,610 ft³/s, answered out of order); a point beside it giving it as the nearest location with discharge; a search by name, two pages by the cursor. The fixtures in Tests/Fixtures/ are its answers.

Again on 2026-10-27: the Potomac at Little Falls by its id (65 series kept), its daily mean discharge from 10-20 to 10-26, a search by name (the example above).

Not verified: a key (none created) and the rate limits (no 429 met; without a key the answers carried no X-RateLimit-* header); a series of more than 50,000 values (the next link is followed as the API's documentation shows).

Responsibility

This package is an independent, unofficial client: it is not affiliated with, endorsed by or sponsored by the U.S. Geological Survey. Its water data are in the U.S. public domain; credit the USGS ("U.S. Geological Survey, Water Data for the Nation"); recent values are provisional, as the README's Terms of use says. Whoever runs the package is responsible for any use made of what it returns: see the README's Responsibility section.

Proposer une modification docs/index.md