usdata 0.23.0__tar.gz → 0.25.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {usdata-0.23.0 → usdata-0.25.0}/PKG-INFO +2 -2
- {usdata-0.23.0 → usdata-0.25.0}/README.md +1 -1
- {usdata-0.23.0 → usdata-0.25.0}/pyproject.toml +1 -1
- {usdata-0.23.0 → usdata-0.25.0}/pyproject.toml.orig +1 -1
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/data/registry.yaml +50 -8
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/coops.py +66 -3
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/goes.py +33 -19
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/__main__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_fetch.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_files.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_grib.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_hurdat2.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_netcdf.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_progress.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/_radar.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cache.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cache_ops.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cite.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/app.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/cache.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/cite.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/doctor.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/inspect.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/cli/progress.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/data/nexrad_sites.csv +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/data/places.csv +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/data/places.sources.json +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/doctor.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/inspect.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/manifest.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/mirror.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/models.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/protocols/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/protocols/erddap.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/protocols/http.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/protocols/listing.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/protocols/s3.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/provenance.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/base.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/fema/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/fema/declarations.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/http.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/coastwatch.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/gfs.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/glm.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/grib_index.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/gsom.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/gsoy.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/hrrr.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/hurdat2.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/ibtracs.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/lcd.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/mrms.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/nbm.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/nexrad.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/nexrad_level3.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/normals.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/nws_vtec.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/rap.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/sites.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/spc.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/noaa/storm_events.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/params.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/usgs/__init__.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/usgs/daily.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/providers/usgs/earthquakes.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/pull.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/py.typed +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/query.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/readers.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/registry.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/selection.py +0 -0
- {usdata-0.23.0 → usdata-0.25.0}/src/usdata/testing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usdata
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
4
4
|
Summary: Unified Python SDK and CLI for discovering, fetching, and tracking provenance of U.S. public scientific data
|
|
5
5
|
Keywords: noaa,usgs,open-data,scientific-data,provenance,reproducible-research,weather,climate,meteorology
|
|
6
6
|
Author: Jake Van Slyke
|
|
@@ -112,7 +112,7 @@ breaking changes.
|
|
|
112
112
|
| [docs.usdata.dev](https://docs.usdata.dev/) | How to use it: [install](https://docs.usdata.dev/install/), [getting started](https://docs.usdata.dev/getting-started/), guides, dataset notes, and reference |
|
|
113
113
|
| [Severe-weather case study](https://usdata.dev/examples/severe-weather-case-study/) | One tornado, six sources, one manifest and lockfile, ending in a citation |
|
|
114
114
|
|
|
115
|
-
Twenty-
|
|
115
|
+
Twenty-six datasets are available today and twenty more are planned, grouped
|
|
116
116
|
by agency and product family in the [catalog](docs/providers/README.md).
|
|
117
117
|
|
|
118
118
|
## How this compares
|
|
@@ -67,7 +67,7 @@ breaking changes.
|
|
|
67
67
|
| [docs.usdata.dev](https://docs.usdata.dev/) | How to use it: [install](https://docs.usdata.dev/install/), [getting started](https://docs.usdata.dev/getting-started/), guides, dataset notes, and reference |
|
|
68
68
|
| [Severe-weather case study](https://usdata.dev/examples/severe-weather-case-study/) | One tornado, six sources, one manifest and lockfile, ending in a citation |
|
|
69
69
|
|
|
70
|
-
Twenty-
|
|
70
|
+
Twenty-six datasets are available today and twenty more are planned, grouped
|
|
71
71
|
by agency and product family in the [catalog](docs/providers/README.md).
|
|
72
72
|
|
|
73
73
|
## How this compares
|
|
@@ -209,30 +209,31 @@ datasets:
|
|
|
209
209
|
system: noaa:goes-r
|
|
210
210
|
domain: weather-satellites
|
|
211
211
|
since: "0.8"
|
|
212
|
-
title: GOES-R ABI
|
|
212
|
+
title: GOES-R ABI Cloud and Moisture Imagery
|
|
213
213
|
description: >-
|
|
214
|
-
Single-channel CONUS
|
|
214
|
+
Single-channel CONUS (ABI-L2-CMIPC) and mesoscale (ABI-L2-CMIPM) imagery from
|
|
215
215
|
GOES-16, 17, 18, and 19 in anonymous NOAA S3 buckets. Select an explicit
|
|
216
|
-
satellite, channel,
|
|
216
|
+
satellite, channel, scan-start interval, and M1 or M2 for mesoscale; each asset is a complete
|
|
217
217
|
NetCDF scene with no geographic or variable subsetting.
|
|
218
|
-
keywords: [satellite, imagery, goes, abi, clouds, infrared, reflectance, netcdf, conus]
|
|
218
|
+
keywords: [satellite, imagery, goes, abi, clouds, infrared, reflectance, netcdf, conus, mesoscale]
|
|
219
219
|
protocol: s3
|
|
220
220
|
homepage: https://registry.opendata.aws/noaa-goes/
|
|
221
221
|
license: US Government Work (public domain)
|
|
222
222
|
temporal_extent: { start: "2017-02-28T00:00:00Z" }
|
|
223
223
|
capabilities: { spatial_subset: false, temporal_subset: true, variable_subset: false }
|
|
224
|
-
summary: GOES CONUS imagery
|
|
224
|
+
summary: GOES CONUS and mesoscale imagery
|
|
225
225
|
formats: [NetCDF4]
|
|
226
|
-
selection: Whole single-channel
|
|
227
|
-
inputs: Satellite, channel, and
|
|
226
|
+
selection: Whole single-channel scenes by inclusive UTC scan-start time and explicit mesoscale sector
|
|
227
|
+
inputs: Satellite, channel, both timestamps; product and sector for mesoscale
|
|
228
228
|
reader: netcdf
|
|
229
229
|
guide: docs/providers/noaa-goes.md
|
|
230
230
|
examples:
|
|
231
231
|
- examples/goes-imagery/example.ipynb
|
|
232
|
+
- examples/goes-mesoscale/example.ipynb
|
|
232
233
|
- examples/event-context/example.ipynb
|
|
233
234
|
resolution:
|
|
234
235
|
spatial: "0.5 km to 2 km at nadir, by ABI band"
|
|
235
|
-
temporal: "
|
|
236
|
+
temporal: "CONUS every 5 minutes; two mesoscale sectors every 60 seconds or one every 30 seconds"
|
|
236
237
|
update_frequency: "New data is added as soon as it's available"
|
|
237
238
|
citation: >-
|
|
238
239
|
NOAA Geostationary Operational Environmental Satellites (GOES) 16, 17, 18 & 19 was
|
|
@@ -641,6 +642,47 @@ datasets:
|
|
|
641
642
|
limits: { max_window: P366D }
|
|
642
643
|
adapter: usdata.providers.noaa.coops:CoopsTidePredictions
|
|
643
644
|
|
|
645
|
+
- id: noaa:coops-currents
|
|
646
|
+
provider: noaa
|
|
647
|
+
status: available
|
|
648
|
+
system: noaa:coops
|
|
649
|
+
domain: ocean-physics
|
|
650
|
+
since: "0.24"
|
|
651
|
+
title: CO-OPS Observed Currents
|
|
652
|
+
description: >-
|
|
653
|
+
Native six-minute current speed and direction from the anonymous CO-OPS
|
|
654
|
+
Data API. Select one alphanumeric station, a positive bin, units, and a
|
|
655
|
+
UTC interval of at most 28 days. Raw CSV preserves gaps and blank values.
|
|
656
|
+
Bin depths can change between deployments; the CSV carries bin number,
|
|
657
|
+
not depth or a verification-quality flag. Predictions, all-bin queries,
|
|
658
|
+
station discovery, and resampling are not included.
|
|
659
|
+
keywords: [currents, current speed, current direction, coastal, ocean, coops, ports, stations, bin]
|
|
660
|
+
protocol: http
|
|
661
|
+
homepage: https://api.tidesandcurrents.noaa.gov/api/prod/
|
|
662
|
+
license: US Government Work (public domain)
|
|
663
|
+
capabilities: { spatial_subset: false, temporal_subset: true, variable_subset: false }
|
|
664
|
+
summary: Coastal current speed and direction
|
|
665
|
+
formats: [CSV]
|
|
666
|
+
selection: Native six-minute observations for one station and explicit bin; at most 28 days
|
|
667
|
+
inputs: Alphanumeric station, positive bin, and both minute-aligned timestamps
|
|
668
|
+
reader: pandas
|
|
669
|
+
guide: docs/providers/noaa-coops-currents.md
|
|
670
|
+
examples:
|
|
671
|
+
- examples/coastal-currents/example.ipynb
|
|
672
|
+
resolution:
|
|
673
|
+
spatial: One current-meter station and bin per request; bin depths depend on deployment
|
|
674
|
+
temporal: Six-minute observations
|
|
675
|
+
citation: >-
|
|
676
|
+
NOAA National Ocean Service, CO-OPS observed currents, accessed via usdata
|
|
677
|
+
terms: https://tidesandcurrents.noaa.gov/disclaimers.html
|
|
678
|
+
variables:
|
|
679
|
+
- { name: "Date Time", units: "UTC", description: "Observation time, requested with time_zone=gmt" }
|
|
680
|
+
- { name: "Speed", units: "cm/s", description: "Observed current speed in metric units; knots with english units" }
|
|
681
|
+
- { name: "Direction", units: "degrees", description: "Observed current direction" }
|
|
682
|
+
- { name: "Bin", description: "Requested bin number, not a depth measurement" }
|
|
683
|
+
limits: { max_window: P28D }
|
|
684
|
+
adapter: usdata.providers.noaa.coops:CoopsCurrents
|
|
685
|
+
|
|
644
686
|
- id: noaa:gsom
|
|
645
687
|
provider: noaa
|
|
646
688
|
status: available
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
"""CO-OPS observed water levels and tide predictions as raw CSV.
|
|
1
|
+
"""CO-OPS observed water levels, currents, and tide predictions as raw CSV.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Water levels and tide predictions share one station model: one seven-digit ``station``, an explicit
|
|
4
4
|
``datum``, optional ``units`` (metric or english), and both timestamps at minute
|
|
5
5
|
precision, requested in UTC; a bare end date means 23:59 on that day.
|
|
6
6
|
Observations span at most 28 days; predictions
|
|
7
7
|
span at most a year on any ``interval`` (six-minute by default, another minute
|
|
8
8
|
step, hourly, or high/low), within NOAA's own limits.
|
|
9
|
+
Currents instead name an alphanumeric station and explicit positive bin, with
|
|
10
|
+
no datum or interval selector. Their native six-minute observations share the
|
|
11
|
+
28-day window and use cm/s (metric) or knots (english).
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
from __future__ import annotations
|
|
12
15
|
|
|
13
16
|
import csv
|
|
14
17
|
import hashlib
|
|
18
|
+
import math
|
|
15
19
|
from collections.abc import Iterator
|
|
16
20
|
from datetime import UTC, datetime, timedelta
|
|
17
21
|
from pathlib import Path
|
|
@@ -25,7 +29,7 @@ from usdata.models import Asset, Protocol, Query, TimeRange
|
|
|
25
29
|
from usdata.protocols import http
|
|
26
30
|
from usdata.providers.base import QueryError
|
|
27
31
|
from usdata.providers.http import HttpProvider
|
|
28
|
-
from usdata.providers.params import choice
|
|
32
|
+
from usdata.providers.params import choice, positive_int
|
|
29
33
|
from usdata.query import LAST_INSTANT
|
|
30
34
|
|
|
31
35
|
DATA_URL = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter"
|
|
@@ -44,6 +48,7 @@ OBSERVATION_COLUMNS = {
|
|
|
44
48
|
"Quality",
|
|
45
49
|
}
|
|
46
50
|
PREDICTION_COLUMNS = {"Date Time", "Prediction"}
|
|
51
|
+
CURRENTS_COLUMNS = {"Date Time", "Speed", "Direction", "Bin"}
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
def _rows(path: Path, asset: Asset, required: set[str]) -> Iterator[dict[str, str]]:
|
|
@@ -150,6 +155,29 @@ class CoopsPredictionParams(CoopsParams):
|
|
|
150
155
|
return token
|
|
151
156
|
|
|
152
157
|
|
|
158
|
+
class CoopsCurrentsParams(BaseModel):
|
|
159
|
+
"""One current-meter station and bin; a bin is not a permanent depth."""
|
|
160
|
+
|
|
161
|
+
model_config = ConfigDict(extra="forbid")
|
|
162
|
+
|
|
163
|
+
station: str = Field(
|
|
164
|
+
description="Required alphanumeric CO-OPS currents station id, for example 'cb0102'."
|
|
165
|
+
)
|
|
166
|
+
bin: Annotated[int, positive_int()] = Field(
|
|
167
|
+
description="Required positive bin number; 0 (all bins) is not supported."
|
|
168
|
+
)
|
|
169
|
+
units: Annotated[str, choice("metric", "english")] = Field(
|
|
170
|
+
default="metric", description="metric (default, cm/s) or english (knots)."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@field_validator("station", mode="before")
|
|
174
|
+
@classmethod
|
|
175
|
+
def _one_station_id(cls, value: object) -> object:
|
|
176
|
+
if not isinstance(value, str) or not value.isascii() or not value.isalnum():
|
|
177
|
+
raise ValueError("must be an alphanumeric string, for example 'cb0102'")
|
|
178
|
+
return value
|
|
179
|
+
|
|
180
|
+
|
|
153
181
|
class _CoopsStation(HttpProvider):
|
|
154
182
|
"""Shared window, request, and validation rules for CO-OPS station products."""
|
|
155
183
|
|
|
@@ -255,3 +283,38 @@ class CoopsTidePredictions(_CoopsStation):
|
|
|
255
283
|
def _validate(self, path: Path, asset: Asset) -> None:
|
|
256
284
|
interval = httpx.URL(asset.href).params.get("interval", "6")
|
|
257
285
|
_validate_predictions(path, asset, interval)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class CoopsCurrents(_CoopsStation):
|
|
289
|
+
"""One station's observed current speed and direction at an explicit bin."""
|
|
290
|
+
|
|
291
|
+
product = "currents"
|
|
292
|
+
label = "current"
|
|
293
|
+
params_model = CoopsCurrentsParams
|
|
294
|
+
|
|
295
|
+
def list_assets(self, query: Query) -> list[Asset]:
|
|
296
|
+
"""Describe one native-interval CSV request; check availability on fetch."""
|
|
297
|
+
params = self.parse_params(query, CoopsCurrentsParams)
|
|
298
|
+
self.reject(query, "bbox", "text", "variables", hint="CO-OPS requires an explicit station")
|
|
299
|
+
start, end = self._window(query, MAX_INTERVAL)
|
|
300
|
+
request = {"bin": str(params.bin), "units": params.units}
|
|
301
|
+
return [self._asset(params.station, start, end, request)]
|
|
302
|
+
|
|
303
|
+
def _validate(self, path: Path, asset: Asset) -> None:
|
|
304
|
+
expected_bin = int(httpx.URL(asset.href).params["bin"])
|
|
305
|
+
count = 0
|
|
306
|
+
for row in _rows(path, asset, CURRENTS_COLUMNS):
|
|
307
|
+
token = row["Bin"]
|
|
308
|
+
if not token.isascii() or not token.isdigit() or int(token) != expected_bin:
|
|
309
|
+
raise ValueError("record does not match the requested bin")
|
|
310
|
+
# Preserve blank measurements and missing timestamps, not invented values.
|
|
311
|
+
for field in ("Speed", "Direction"):
|
|
312
|
+
if row[field]:
|
|
313
|
+
value = float(row[field])
|
|
314
|
+
if not math.isfinite(value) or value < 0:
|
|
315
|
+
raise ValueError(f"invalid current {field.lower()}")
|
|
316
|
+
if field == "Direction" and value > 360:
|
|
317
|
+
raise ValueError("current direction must be between 0 and 360 degrees")
|
|
318
|
+
count += 1
|
|
319
|
+
if not count:
|
|
320
|
+
raise ValueError("no current observations returned")
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
"""GOES ABI
|
|
1
|
+
"""GOES ABI Cloud and Moisture Imagery from anonymous NOAA S3 buckets.
|
|
2
2
|
|
|
3
3
|
Require ``satellite`` (16, 17, 18, or 19), ``channel`` (1--16 or C01--C16), and
|
|
4
|
-
both timestamps at most seven days apart.
|
|
5
|
-
``ABI-L2-CMIPC
|
|
4
|
+
both timestamps at most seven days apart. ``product`` defaults to CONUS
|
|
5
|
+
``ABI-L2-CMIPC``; ``ABI-L2-CMIPM`` requires ``sector=M1`` or ``M2``.
|
|
6
|
+
Select whole single-channel NetCDF files by inclusive
|
|
6
7
|
scan-start time, never by scan overlap.
|
|
7
8
|
Geographic and variable subsetting are not available for these archived files.
|
|
8
9
|
``list_scans`` is shared with the GLM adapter, which selects files under the
|
|
@@ -12,10 +13,10 @@ same bucket layout by the same inclusive start-time rule.
|
|
|
12
13
|
from __future__ import annotations
|
|
13
14
|
|
|
14
15
|
import re
|
|
15
|
-
from collections.abc import Callable
|
|
16
|
+
from collections.abc import Callable
|
|
16
17
|
from datetime import UTC, datetime, timedelta
|
|
17
18
|
from pathlib import Path
|
|
18
|
-
from typing import Annotated
|
|
19
|
+
from typing import Annotated, Self
|
|
19
20
|
|
|
20
21
|
import httpx
|
|
21
22
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
@@ -24,13 +25,15 @@ from usdata.models import Asset, Protocol, Query, TimeRange
|
|
|
24
25
|
from usdata.protocols import s3
|
|
25
26
|
from usdata.providers.base import QueryError
|
|
26
27
|
from usdata.providers.http import HttpProvider
|
|
27
|
-
from usdata.providers.params import int_range
|
|
28
|
+
from usdata.providers.params import choice, int_range
|
|
28
29
|
|
|
29
30
|
PRODUCT = "ABI-L2-CMIPC"
|
|
31
|
+
MESOSCALE_PRODUCT = "ABI-L2-CMIPM"
|
|
30
32
|
PUBLIC_START = datetime(2017, 2, 28, tzinfo=UTC)
|
|
31
33
|
MAX_WINDOW = timedelta(days=7)
|
|
32
34
|
KEY_RE = re.compile(
|
|
33
|
-
r"OR_ABI-L2-
|
|
35
|
+
r"OR_ABI-L2-CMIP(?P<sector>C|M[12])-M[346]C(?P<channel>0[1-9]|1[0-6])"
|
|
36
|
+
r"_G(?P<satellite>1[6-9])"
|
|
34
37
|
r"_s(?P<start>\d{14})_e(?P<end>\d{14})_c\d{14}\.nc"
|
|
35
38
|
)
|
|
36
39
|
|
|
@@ -54,7 +57,14 @@ class GoesAbiParams(BaseModel):
|
|
|
54
57
|
channel: Annotated[int, int_range(1, 16)] = Field(
|
|
55
58
|
description="Required ABI channel, 1 to 16 or C01 to C16."
|
|
56
59
|
)
|
|
57
|
-
product: str
|
|
60
|
+
product: Annotated[str, choice(PRODUCT, MESOSCALE_PRODUCT)] = Field(
|
|
61
|
+
default=PRODUCT,
|
|
62
|
+
description="ABI-L2-CMIPC (CONUS, default) or ABI-L2-CMIPM (mesoscale).",
|
|
63
|
+
)
|
|
64
|
+
sector: Annotated[str, choice("M1", "M2")] | None = Field(
|
|
65
|
+
default=None,
|
|
66
|
+
description="Required for ABI-L2-CMIPM: M1 or M2. Omit for CONUS.",
|
|
67
|
+
)
|
|
58
68
|
|
|
59
69
|
@field_validator("channel", mode="before")
|
|
60
70
|
@classmethod
|
|
@@ -63,13 +73,13 @@ class GoesAbiParams(BaseModel):
|
|
|
63
73
|
text = value.strip() if isinstance(value, str) else value
|
|
64
74
|
return text[1:] if isinstance(text, str) and text.startswith("C") else text
|
|
65
75
|
|
|
66
|
-
@model_validator(mode="
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if
|
|
71
|
-
raise ValueError(
|
|
72
|
-
return
|
|
76
|
+
@model_validator(mode="after")
|
|
77
|
+
def _sector_matches_product(self) -> Self:
|
|
78
|
+
if self.product == MESOSCALE_PRODUCT and self.sector is None:
|
|
79
|
+
raise ValueError("sector=M1 or M2 is required for product=ABI-L2-CMIPM")
|
|
80
|
+
if self.product == PRODUCT and self.sector is not None:
|
|
81
|
+
raise ValueError("sector is only supported with product=ABI-L2-CMIPM")
|
|
82
|
+
return self
|
|
73
83
|
|
|
74
84
|
|
|
75
85
|
def list_scans(
|
|
@@ -120,7 +130,7 @@ def list_scans(
|
|
|
120
130
|
|
|
121
131
|
|
|
122
132
|
class GoesAbi(HttpProvider):
|
|
123
|
-
"""Single-channel CONUS ABI imagery
|
|
133
|
+
"""Single-channel CONUS or mesoscale ABI imagery with explicit sector selection."""
|
|
124
134
|
|
|
125
135
|
params_model = GoesAbiParams
|
|
126
136
|
|
|
@@ -139,16 +149,20 @@ class GoesAbi(HttpProvider):
|
|
|
139
149
|
raise QueryError("GOES requests must span at most 7 days; split longer intervals")
|
|
140
150
|
satellite, channel = params.satellite, params.channel
|
|
141
151
|
if end < PUBLIC_START:
|
|
142
|
-
raise QueryError("GOES
|
|
152
|
+
raise QueryError("GOES ABI public observations begin on 2017-02-28")
|
|
143
153
|
start = max(start, PUBLIC_START)
|
|
144
154
|
return list_scans(
|
|
145
155
|
self._http(),
|
|
146
156
|
f"noaa-goes{satellite}",
|
|
147
|
-
|
|
157
|
+
params.product,
|
|
148
158
|
start,
|
|
149
159
|
end,
|
|
150
160
|
KEY_RE,
|
|
151
|
-
lambda m:
|
|
161
|
+
lambda m: (
|
|
162
|
+
int(m["satellite"]) == satellite
|
|
163
|
+
and int(m["channel"]) == channel
|
|
164
|
+
and m["sector"] == (params.sector or "C")
|
|
165
|
+
),
|
|
152
166
|
self.dataset.id,
|
|
153
167
|
)
|
|
154
168
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|