DHI.Services.USGS — Internal Guide (Time Series)¶
Provider for USGS Daily Values (DV) from
waterservices.usgs.govinto the unifiedDHI.Services.TimeSeriesmodel. Covers: repository behavior, ID rules, date scoping, flags/metadata, EUM mapping & conversion, client/query details, Web API wiring.
What this provider gives you¶
| Scope | Class | Base type | Read | Write | Grouped | Notes |
|---|---|---|---|---|---|---|
| USGS Daily Values | TimeSeriesRepository |
BaseTimeSeriesRepository<string,double> |
✓ | ✗ | — | Targets DV endpoint; yields TimeSeries<string,double> with flags + rich metadata. |
Key traits
- ID format:
{AgencyCode}:{SiteNumber}:{ParameterCode}:{StatisticsCode}(e.g.,USGS:01100000:00060:00003). - Date window: optional defaults from the repository’s base address (
startDT,endDT; case-insensitive). If omitted, USGS applies its own defaults. - Flags: USGS qualifiers (
A,P,e, …) preserved per sample inDataWFlag.Flags. - Units/quantity: auto-mapped to EUM via
UsgsToEum(with conversion factor when needed). - Format: WaterML 1.1 only; other formats throw
NotSupportedException.
Quick start¶
using DHI.Services.Provider.USGS;
using DHI.Services.TimeSeries;
var repo = new TimeSeriesRepository(
"https://waterservices.usgs.gov/nwis/dv/?startDT=2024-01-01&endDT=2024-12-31");
var id = "USGS:11458000:00060:00003"; // Agency:Site:Parameter:Statistic
var tsMaybe = repo.Get(id);
if (!tsMaybe.HasValue) return; // HTTP error or not found (see behavior below)
var ts = (UsgsTimeSeries)tsMaybe.Value;
var data = ts.Data; // ITimeSeriesData<double>
var flags = ts.DataWFlag.Flags; // qualifiers per value
Console.WriteLine($"{ts.Name} [{ts.Quantity} / {ts.Unit}] points={data.Count}");
Repository construction & default dates¶
// Any DV base URL is fine; query here establishes defaults for Get()
var repo = new TimeSeriesRepository(
"https://waterservices.usgs.gov/nwis/dv/?startDT=2025-01-01&endDT=2025-06-30");
- The constructor:
- Accepts either a plain base URL (
…/dv/) or one with a query string. - Parses
startDT/endDTfrom the base address (case-insensitive) and stores them as default window. - Validates base address non-null; throws
ArgumentNullExceptionwhen null. - Parses dates as
yyyy-MM-dd; bad formats throw aFormatExceptionwith a helpful message.
- Accepts either a plain base URL (
- The underlying HTTP client enables gzip/deflate and enforces success status.
Time series IDs (validation)¶
Constructed/validated by UsgsTimeSeriesId:
| Field | Constraints (regex) | Example |
|---|---|---|
| AgencyCode | ^[A-Z0-9]{2,20}$ |
USGS |
| SiteNumber | ^[0-9]{8,20}$ |
01100000 |
| ParameterCode | ^[0-9]{5}$ |
00060 |
| StatisticsCode | ^[0-9]{5}$ |
00003 |
- The raw string is upper-cased, trimmed, and split by
:. - Any violation throws
ArgumentException.
Reading APIs (per ID)¶
Maybe<TimeSeries<string,double>> Get(string id);
Maybe<ITimeSeriesData<double>> GetValues(string id);
Maybe<DataPoint<double>> GetValue(string id, DateTime at);
Maybe<DataPoint<double>> GetFirstValueAfter(string id, DateTime t);
Maybe<DataPoint<double>> GetLastValueBefore(string id, DateTime t);
Behavior
Get(id)composes a DV request using the parsed ID (+ default dates if present), fetches WaterML 1.1, and hydrates aUsgsTimeSeries.- On
HttpRequestExceptionor “time series not found in response”, the repo returnsMaybe.Empty(exceptions are swallowed deliberately). - The first/last/value helpers call
Get(id)internally and then search the hydrated series. Always checkHasValuebefore accessing.
Guard array bounds if you replicate the “search index” patterns yourself; the repository assumes non-empty series.
What comes back (data, flags, metadata)¶
When a UsgsTimeSeries is hydrated:
- Data →
TimeSeriesDataWFlag<double,string>DateTimes+Values(double?)Flags= USGS qualifiers per sample (DataWFlag.Flags[i])
- EUM mapping from
UsgsToEum:Quantity←eumItemUnit←eumUnitDataType← mapped from USGS value type (e.g., “Step Accumulated”)Dimension← dimensionality string if known
- Metadata (set on first hydration; selected keys):
variableCode,variableName,variableDescription,unitCode, valuevalueTypesiteName,siteCode,Latitude,LongitudetimeZoneInfo/*(includes parsed default time zone from offset, preferring US & Canada zones when offsets match)qualifier/<code>human-readable descriptionsnote/<title>,allDisclaimersnoDataValue- Always updated:
lastHydratedAt(UTC),lastQueryUrl,queryUrlHistory(append-only)
EUM mapping & value conversion (UsgsToEum)¶
- Maps USGS parameter codes → EUM (item, unit, default data type, dimension).
- Exposes per-code conversion factor
Factor = multiplier / divider(applied only when ≠ 1).
Examples:
// Lookup EUM info
var m = UsgsToEum.toEumItem("00060"); // discharge
Console.WriteLine($"item={m["eumItem"]}, unit={m["eumUnit"]}, type={m["valueType"]}");
// Convert values explicitly (if you want numeric values in EUM)
var eumValues = UsgsToEum.ConvertValuesOrNulls(ts.Data.Values, "00060");
The provider already sets EUM fields on the
TimeSeriesentity. Only convert numeric values if you need the numbers themselves in EUM.
Supported codes
See the _lookup dictionary in UsgsToEum (includes flow, stage, temperature, precipitation, etc.) — unknown codes fall back to a default.
Building requests explicitly (advanced)¶
When you need multi-site, spatial filters, or ad-hoc windows, use the client & query parameters directly.
var client = new UsgsWaterServicesClient("https://waterservices.usgs.gov/nwis/dv/");
// At least one “major filter” must be supplied in this ctor (sites/stateCd/huc/bBox/countyCd)
var qp = new UsgsWaterServicesQueryParameters(sites: "11458000,11467890");
// qp.SetTimeRange(start, end) or qp.SetTimeRange(daysBackFromToday)
var url = client.BuildUrl(qp.ToDictionary()); // fully composed URL
var xml = client.FetchDailyValues(qp); // WaterML 1.1 as string
var ts = new UsgsTimeSeries("USGS:11458000:00060:00003", "USGS:11458000:00060:00003");
client.FetchDailyValuesToTimeSeries(qp, ts, ignoreTimeSeriesNotFound: false);
Notes
UsgsWaterServicesQueryParametersaddsformat=waterml,1.1automatically.FetchDailyValuesToTimeSeriespopulates only if it finds an exact<timeSeries name="…">matchingts.Idin the response.- If the response isn’t WaterML 1.1, hydration throws
NotSupportedException.
Web API “Connections” wiring (example)¶
{
"$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
"usgs-dv": {
"$type": "DHI.Services.TimeSeries.WebApi.TimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
"ConnectionString": "https://waterservices.usgs.gov/nwis/dv/?startDT=2024-01-01&endDT=2024-12-31",
"RepositoryType": "DHI.Services.Provider.USGS.TimeSeriesRepository, DHI.Services.Provider.USGS",
"Name": "USGS Daily Values",
"Id": "usgs-dv"
}
}
Typical HTTP calls (URL-encode :):
GET /api/timeseries/usgs-dv/USGS%3A11458000%3A00060%3A00003/values
GET /api/timeseries/usgs-dv/USGS%3A11458000%3A00060%3A00003/value?at=2024-06-01
GET /api/timeseries/usgs-dv/USGS%3A11458000%3A00060%3A00003/firstValueAfter?at=2024-06-01
GET /api/timeseries/usgs-dv/USGS%3A11458000%3A00060%3A00003/lastValueBefore?at=2024-06-01
Troubleshooting & guidance¶
- Format: Only WaterML 1.1 is supported.
- Date windows: Prefer bounded
startDT/endDTto reduce payloads and avoid USGS defaults. - Empty results:
Get(id)returns empty Maybe on HTTP errors or “series not found”. - Qualifiers: Inspect
DataWFlag.Flags[i]; human-readable texts live underqualifier/<code>in metadata. - Units: EUM fields are set on the entity; convert values with
UsgsToEumonly when you need numeric value conversion. - Index guards: When scanning for first/last values, check bounds before indexing.
End-to-end example¶
var repo = new TimeSeriesRepository(
"https://waterservices.usgs.gov/nwis/dv/?startDT=2024-01-01&endDT=2024-03-31");
var id = "USGS:11458000:00060:00003";
var tsMaybe = repo.Get(id);
if (!tsMaybe.HasValue) throw new Exception("USGS series not found.");
var ts = (UsgsTimeSeries)tsMaybe.Value;
Console.WriteLine($"{ts.Metadata["siteName"]} ({ts.Metadata["siteCode"]})");
Console.WriteLine($"{ts.Metadata["variableName"]} [{ts.Unit}] — {ts.DataType}");
var data = ts.Data;
var flags = ts.DataWFlag.Flags;
for (int i = 0; i < Math.Min(data.Count, 3); i++)
{
Console.WriteLine($"{data.DateTimes[i]:yyyy-MM-dd}: {data.Values[i]} ({flags[i]})");
}
// Convert numeric values to EUM if desired
var eumVals = UsgsToEum.ConvertValuesOrNulls(data.Values, ts.Metadata["variableCode"].ToString());