eo-fetch-api 1.0.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.
- eo_fetch_api-1.0.0/LICENSE +21 -0
- eo_fetch_api-1.0.0/PKG-INFO +230 -0
- eo_fetch_api-1.0.0/README.md +248 -0
- eo_fetch_api-1.0.0/packaging/README_PYPI.md +180 -0
- eo_fetch_api-1.0.0/pyproject.toml +92 -0
- eo_fetch_api-1.0.0/setup.cfg +4 -0
- eo_fetch_api-1.0.0/src/eo_fetch/__init__.py +48 -0
- eo_fetch_api-1.0.0/src/eo_fetch/__main__.py +3 -0
- eo_fetch_api-1.0.0/src/eo_fetch/api.py +288 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/__init__.py +71 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/base.py +28 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/cds.py +174 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/credentials.py +31 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/ecmwf.py +135 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/esgf.py +231 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/eumetsat.py +172 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/http_index.py +161 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/local.py +45 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/marine.py +158 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/nasa.py +240 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/s3.py +136 -0
- eo_fetch_api-1.0.0/src/eo_fetch/backends/stac.py +237 -0
- eo_fetch_api-1.0.0/src/eo_fetch/bridge.py +53 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/__init__.py +5 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/copernicus_ecmwf.py +130 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/descriptions.py +116 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/dwd.py +50 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/eumetsat.py +42 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/nasa.py +37 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/noaa.py +67 -0
- eo_fetch_api-1.0.0/src/eo_fetch/catalog/stac.py +88 -0
- eo_fetch_api-1.0.0/src/eo_fetch/checkreaders.py +142 -0
- eo_fetch_api-1.0.0/src/eo_fetch/cli.py +469 -0
- eo_fetch_api-1.0.0/src/eo_fetch/discovery.py +111 -0
- eo_fetch_api-1.0.0/src/eo_fetch/downloader.py +112 -0
- eo_fetch_api-1.0.0/src/eo_fetch/manifest.py +73 -0
- eo_fetch_api-1.0.0/src/eo_fetch/models.py +56 -0
- eo_fetch_api-1.0.0/src/eo_fetch/plotting.py +178 -0
- eo_fetch_api-1.0.0/src/eo_fetch/readers/__init__.py +188 -0
- eo_fetch_api-1.0.0/src/eo_fetch/readers/generic.py +83 -0
- eo_fetch_api-1.0.0/src/eo_fetch/readers/radolan.py +112 -0
- eo_fetch_api-1.0.0/src/eo_fetch/readers/satellite.py +170 -0
- eo_fetch_api-1.0.0/src/eo_fetch/readers/unpack.py +81 -0
- eo_fetch_api-1.0.0/src/eo_fetch/registry.py +70 -0
- eo_fetch_api-1.0.0/src/eo_fetch/regrid.py +125 -0
- eo_fetch_api-1.0.0/src/eo_fetch/styles.py +223 -0
- eo_fetch_api-1.0.0/src/eo_fetch/timeguess.py +63 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/PKG-INFO +230 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/SOURCES.txt +68 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/dependency_links.txt +1 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/entry_points.txt +2 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/requires.txt +30 -0
- eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/top_level.txt +1 -0
- eo_fetch_api-1.0.0/tests/test_api.py +102 -0
- eo_fetch_api-1.0.0/tests/test_bridge.py +46 -0
- eo_fetch_api-1.0.0/tests/test_core.py +203 -0
- eo_fetch_api-1.0.0/tests/test_discovery.py +212 -0
- eo_fetch_api-1.0.0/tests/test_ecmwf_cds.py +185 -0
- eo_fetch_api-1.0.0/tests/test_esgf.py +205 -0
- eo_fetch_api-1.0.0/tests/test_eumetsat.py +146 -0
- eo_fetch_api-1.0.0/tests/test_http_index.py +146 -0
- eo_fetch_api-1.0.0/tests/test_login.py +53 -0
- eo_fetch_api-1.0.0/tests/test_login_cdse.py +52 -0
- eo_fetch_api-1.0.0/tests/test_marine.py +130 -0
- eo_fetch_api-1.0.0/tests/test_nasa.py +202 -0
- eo_fetch_api-1.0.0/tests/test_plotting.py +199 -0
- eo_fetch_api-1.0.0/tests/test_readers.py +266 -0
- eo_fetch_api-1.0.0/tests/test_s3.py +216 -0
- eo_fetch_api-1.0.0/tests/test_stac.py +202 -0
- eo_fetch_api-1.0.0/tests/test_unpack.py +116 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Umut Kirdemir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eo-fetch-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: One search-and-download interface, with verified downloads and manifests, for Earth-observation and climate data providers.
|
|
5
|
+
Author: Umut Kirdemir
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: earth observation,climate,satellite,download,meteorology,EUMETSAT,NASA,Copernicus,ERA5,CMIP
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
17
|
+
Requires-Python: <3.14,>=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: truststore
|
|
22
|
+
Requires-Dist: eumdac
|
|
23
|
+
Requires-Dist: earthaccess
|
|
24
|
+
Requires-Dist: pystac-client
|
|
25
|
+
Requires-Dist: planetary-computer
|
|
26
|
+
Requires-Dist: s3fs
|
|
27
|
+
Requires-Dist: cdsapi
|
|
28
|
+
Requires-Dist: ecmwf-opendata
|
|
29
|
+
Requires-Dist: copernicusmarine
|
|
30
|
+
Requires-Dist: intake-esgf
|
|
31
|
+
Requires-Dist: numpy
|
|
32
|
+
Requires-Dist: xarray
|
|
33
|
+
Requires-Dist: netcdf4
|
|
34
|
+
Requires-Dist: h5netcdf
|
|
35
|
+
Requires-Dist: h5py
|
|
36
|
+
Requires-Dist: zarr
|
|
37
|
+
Requires-Dist: rioxarray
|
|
38
|
+
Requires-Dist: pyproj
|
|
39
|
+
Requires-Dist: pyresample
|
|
40
|
+
Requires-Dist: satpy
|
|
41
|
+
Requires-Dist: eccodes
|
|
42
|
+
Requires-Dist: earthkit-data
|
|
43
|
+
Requires-Dist: earthkit-plots
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest; extra == "dev"
|
|
46
|
+
Requires-Dist: ruff; extra == "dev"
|
|
47
|
+
Requires-Dist: build; extra == "dev"
|
|
48
|
+
Requires-Dist: twine; extra == "dev"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# eo-fetch
|
|
52
|
+
|
|
53
|
+
One search-and-download interface, with verified downloads and reproducible manifests, on top of the libraries that already talk to Earth-observation and climate data providers. Readers and map plots for what you download are included.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import eo_fetch as eof
|
|
57
|
+
|
|
58
|
+
found = eof.search("dwd", "radolan-yw", limit=2) # same query model for every provider
|
|
59
|
+
eof.download(found, "data/dwd") # verified files + manifest.json
|
|
60
|
+
ds = eof.open(f"data/dwd/{found[0].filename}") # xarray Dataset
|
|
61
|
+
eof.plot(ds, "plots/radar.png") # map in the shared style registry
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Every provider below was tested on a real download; each provider card states the date it was last verified (`eof.providers()`).
|
|
65
|
+
|
|
66
|
+
## What it does
|
|
67
|
+
|
|
68
|
+
- **One query model** for every provider: product, time range, area, keyword, plus provider-specific options.
|
|
69
|
+
- **Verified, resumable downloads**: files are written under a temporary name, checked for size and checksum when the provider announces them, retried with back-off, and never exceed the number of connections a service allows.
|
|
70
|
+
- **A manifest** for every download run: what was fetched, from where, when, sizes, SHA-256 checksums, and the provider's licence and attribution. `eof.verify` checks the files against it later.
|
|
71
|
+
- **A provider registry** with access type (none, account, licence), latency, licence, attribution and "last verified" date.
|
|
72
|
+
- **Readers**: NetCDF, HDF5, Zarr, GRIB, GeoTIFF/JPEG 2000/ESRI ASCII, gzip/bzip2/zip containers, and dedicated readers for MSG SEVIRI (native and level-2 GRIB), MTG FCI level-1c, GOES ABI and DWD radar composites.
|
|
73
|
+
- **Maps**: one style registry on top of earthkit-plots; projected data (radar, geostationary) is regridded to latitude/longitude first. NetCDF/Zarr conversion and an earthkit-data bridge.
|
|
74
|
+
- Backends wrap existing libraries (`eumdac`, `earthaccess`, `pystac-client`, `earthkit-data`, `copernicusmarine`, `intake-esgf`, `cdsapi`, `ecmwf-opendata`). eo-fetch does not replace them.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
Python 3.12 or 3.13 (the earthkit packages have no wheels for 3.14 yet).
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
pip install eo-fetch-api
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
One install includes every provider, the readers and the plots. The package is imported as `eo_fetch`: `import eo_fetch as eof`.
|
|
85
|
+
|
|
86
|
+
## Python API
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import eo_fetch as eof
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Options that are specific to a provider are keyword arguments.
|
|
93
|
+
|
|
94
|
+
### Find product names
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
for match in eof.products("precipitation"): # aliases of all providers
|
|
98
|
+
print(match.provider, match.product, "-", match.description)
|
|
99
|
+
eof.products(provider="dwd") # one provider (+ top-level folders)
|
|
100
|
+
eof.products("soil moisture", "nasa") # with a provider: live catalogue too
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`eof.products(text="", provider=None, *, live=None, browse=None)` returns `eof.ProductMatch` objects (`provider`, `product`, `description`, `source`); pass `match.product` to `eof.search`.
|
|
104
|
+
|
|
105
|
+
### Search
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
found = eof.search(
|
|
109
|
+
"nasa",
|
|
110
|
+
"imerg-late-daily",
|
|
111
|
+
start="2026-10-01",
|
|
112
|
+
end="2026-10-03", # ISO strings, dates or datetimes (end exclusive)
|
|
113
|
+
bbox=(5, 47, 16, 55), # west, south, east, north
|
|
114
|
+
)
|
|
115
|
+
print(len(found), f"{found.megabytes:.1f} MB")
|
|
116
|
+
for item in found:
|
|
117
|
+
print(item.time, item.key, item.size)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`eof.search(provider, product="", *, start, end, bbox, text, limit, params, **options)` returns `eof.Results`, a normal list of `eof.Item` that also remembers how to download itself. Provider options go in as keywords or in a `params` dict; lists become comma-separated values:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
eof.search(
|
|
124
|
+
"cdse",
|
|
125
|
+
"sentinel-2",
|
|
126
|
+
start="2026-10-28",
|
|
127
|
+
end="2026-10-30",
|
|
128
|
+
bbox=(8.5, 53.4, 9.5, 53.8),
|
|
129
|
+
assets=["B04_60m", "SCL_60m"],
|
|
130
|
+
limit=2,
|
|
131
|
+
)
|
|
132
|
+
eof.search(
|
|
133
|
+
"esgf",
|
|
134
|
+
"CMIP6",
|
|
135
|
+
variable_id="tas",
|
|
136
|
+
experiment_id="historical",
|
|
137
|
+
source_id="CanESM5",
|
|
138
|
+
member_id="r1i1p1f1",
|
|
139
|
+
table_id="Amon",
|
|
140
|
+
)
|
|
141
|
+
eof.search(
|
|
142
|
+
"cds",
|
|
143
|
+
"era5-single-levels",
|
|
144
|
+
start="2026-01-01",
|
|
145
|
+
end="2026-02-01",
|
|
146
|
+
variable="2m_temperature",
|
|
147
|
+
bbox=(5, 47, 16, 55),
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
An unknown option raises `ValueError` with the accepted names. Filter or slice the results freely (`found[:3]`, `found.where(lambda item: item.size < 5e6)`); they keep working with `eof.download`.
|
|
152
|
+
|
|
153
|
+
### Download and verify
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
results = eof.download(found, "data/imerg") # also writes data/imerg/manifest.json
|
|
157
|
+
failed = [r for r in results if r.status == "failed"] # downloaded | skipped | failed
|
|
158
|
+
problems = eof.verify("data/imerg/manifest.json") # [] when every file matches
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`eof.download(results, dest, *, workers=None, retries=3, manifest=None, on_result=None)` returns a list of `eof.Result` (`item`, `path`, `status`, `size`, `sha256`, `seconds`, `error`). Failures do not raise. Files that already exist with the right size and checksum are skipped, so the same call resumes an interrupted run. `on_result` is called as each file finishes (for progress output).
|
|
162
|
+
|
|
163
|
+
### Open, plot, convert
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
ds = eof.open(results[0].path) # path of the first downloaded file
|
|
167
|
+
path = eof.plot(ds, "plots/imerg.png", bbox=(5, 47, 16, 55), style="precip-daily")
|
|
168
|
+
eof.convert("data/era5/era5.grib", "data/era5/era5.nc") # or .zarr
|
|
169
|
+
ekd = eof.to_earthkit("data/era5/era5.grib") # earthkit-data object
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`eof.open(path, format=None, **options)` returns an `xarray.Dataset`; the format is detected from the name and content (see below). `eof.plot(source, output=None, *, variable, style, domain, resolution, bbox, title)` accepts a file or a Dataset and returns the image path. The style names are the keys of `eo_fetch.styles.STYLES`.
|
|
173
|
+
|
|
174
|
+
### Providers, credentials, diagnostics
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
for info in eof.providers(): # ProviderInfo cards
|
|
178
|
+
print(info.name, info.auth, info.verified)
|
|
179
|
+
eof.provider("cds").notes # how to use the provider, options, limits
|
|
180
|
+
eof.collections("copernicus-marine", "sea surface temperature")
|
|
181
|
+
eof.login("cds") # hidden prompts; saves to your user folder
|
|
182
|
+
eof.doctor("nasa") # prints what is missing; True when all is well
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Lower-level pieces (`eo_fetch.backends`, `eo_fetch.readers`, `eo_fetch.regrid`) are importable too and are what the functions above are built on. To add a provider, implement the `Backend` protocol (`search(query)`, `fetch(item, target)`, `max_connections`) and register a `ProviderInfo` card.
|
|
186
|
+
|
|
187
|
+
## Providers
|
|
188
|
+
|
|
189
|
+
`eof.providers()` always shows the current list and verification dates.
|
|
190
|
+
|
|
191
|
+
| Provider | What | Access |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| `eumetsat` | EUMETSAT Data Store: Meteosat, MTG, Metop, Sentinel-3/5P/6, CM SAF (all collections) | licence per collection |
|
|
194
|
+
| `noaa` | GOES-R (16, 18, 19) and other public NOAA buckets on AWS | none |
|
|
195
|
+
| `dwd` | DWD open data tree: radar, ICON models, climate data centre | none |
|
|
196
|
+
| `nasa` | every collection of NASA's CMR (IMERG, MERRA-2, OMI, SMAP, ...) | free Earthdata account |
|
|
197
|
+
| `planetary-computer`, `earth-search` | STAC catalogues: Sentinel, Landsat, land cover, climate | none |
|
|
198
|
+
| `cdse` | Copernicus Data Space (Sentinel) via STAC and S3 | free account + S3 keys |
|
|
199
|
+
| `ecmwf-open-data` | IFS and ensemble forecasts by parameter and lead time | none |
|
|
200
|
+
| `cds` | ERA5, ERA5-Land and other Climate Data Store datasets | free account, licence per dataset |
|
|
201
|
+
| `copernicus-marine` | ocean physics, biogeochemistry, sea ice, waves | free account |
|
|
202
|
+
| `esgf` | CMIP climate-model output (CMIP5 and CMIP6 tested; CMIP3/7, CORDEX-CMIP6, obs4MIPs as far as the indices serve them) | none |
|
|
203
|
+
|
|
204
|
+
Each card (`eof.provider(name)`) lists licence, attribution, latency and the options of that provider. Respect the licence and attribution when you publish results; the manifest records both.
|
|
205
|
+
|
|
206
|
+
## Credentials
|
|
207
|
+
|
|
208
|
+
Providers with accounts have a one-time `eof.login("NAME")` (hidden prompts; the login is saved as a plain-text file in your user folder, and deleting the file logs you out). `eof.doctor("NAME")` tells you what is missing. You can also set the environment variables, which take priority, or write the file yourself. eo-fetch never stores credentials anywhere else, never writes them into manifests or download folders, and sends them only to the provider.
|
|
209
|
+
|
|
210
|
+
| Provider | `eof.login` writes | Or use |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `nasa` (Earthdata) | `~/.netrc` | `EARTHDATA_USERNAME` and `EARTHDATA_PASSWORD`, or `EARTHDATA_TOKEN`; approve the provider's application once at urs.earthdata.nasa.gov |
|
|
213
|
+
| `eumetsat` | `~/.eumdac/credentials` | `EUMETSAT_KEY` and `EUMETSAT_SECRET`; a licence per collection |
|
|
214
|
+
| `cds` (ERA5) | `~/.cdsapirc` | `CDSAPI_URL` and `CDSAPI_KEY`; accept each dataset's licence on its page |
|
|
215
|
+
| `copernicus-marine` | `~/.copernicusmarine/.copernicusmarine-credentials` (written by the toolbox) | `COPERNICUSMARINE_SERVICE_USERNAME` and `COPERNICUSMARINE_SERVICE_PASSWORD` |
|
|
216
|
+
| `cdse` | `~/.eo-fetch/cdse` (`key,secret`) | `CDSE_S3_KEY` and `CDSE_S3_SECRET` from the Copernicus Data Space S3 key manager (searching needs no keys) |
|
|
217
|
+
| `dwd`, `noaa`, `earth-search`, `planetary-computer`, `ecmwf-open-data`, `esgf` | none needed | |
|
|
218
|
+
|
|
219
|
+
## Which files can be opened?
|
|
220
|
+
|
|
221
|
+
`eof.open` detects the format from the file name and content. In the last full test, which tried the smallest recent file of every product alias of every provider, these opened: all GOES-18/19 products, SEVIRI (L1.5, IODC, RSS, cloud mask, cloud top height), FCI level 1c and cloud mask, MTG precipitation and lightning, CM SAF products, DWD radar composites, ICON-EU, DWD satellite radiation and NOAA GFS GRIB. Sentinel-2 JPEG 2000, ERA5 GRIB/NetCDF, ECMWF open data, Copernicus Marine and CMIP6 NetCDF were opened in the provider tests. Unknown formats raise a clear "no reader" error that lists the supported formats.
|
|
222
|
+
|
|
223
|
+
Known limits:
|
|
224
|
+
|
|
225
|
+
- Not readable yet: BUFR (ASCAT, RAP), DWD station zips and radar `.tar` composites, NEXRAD.
|
|
226
|
+
- ICON icosahedral grids open only as a flat list of values (they need DWD's grid files for coordinates).
|
|
227
|
+
- OMI and SMAP HDF5 files open but are not plotted (2-D latitude/longitude arrays).
|
|
228
|
+
- Plots draw the first entry of extra dimensions (time, level, band). Categorical rasters such as Sentinel-2 `SCL` have no dedicated style yet.
|
|
229
|
+
- CMIP6 searches may warn about a publishing error in the new STAC indices of ESGF; the warning is from `intake-esgf`. Switch those indices off with `indices="-discovery.east.esgf.io,-discovery.west.esgf.io"` in `eof.search`. Older projects (CMIP5, CMIP3) are only in the legacy indices: add `indices="all"`; some legacy nodes may be offline and are reported as warnings.
|
|
230
|
+
- On Windows, paths are limited to 260 characters; use a short destination folder. A harmless `Error in sys.excepthook` message can appear at exit after reading GeoTIFF/JPEG 2000 files.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# eo-fetch
|
|
2
|
+
|
|
3
|
+
One search-and-download interface, with verified downloads and reproducible manifests, on top of the libraries that already talk to Earth-observation and climate data providers. Readers and map plots for what you download are included.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import eo_fetch as eof
|
|
7
|
+
|
|
8
|
+
found = eof.search("dwd", "radolan-yw", limit=2) # same query model for every provider
|
|
9
|
+
eof.download(found, "data/dwd") # verified files + manifest.json
|
|
10
|
+
ds = eof.open(f"data/dwd/{found[0].filename}") # xarray Dataset
|
|
11
|
+
eof.plot(ds, "plots/radar.png") # map in the shared style registry
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Status: pre-1.0. Every provider below was tested on a real download; each provider card states the date it was last verified (`eo-fetch providers`).
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
- **One query model** for every provider: product, time range, area, keyword, plus provider-specific options.
|
|
19
|
+
- **Verified, resumable downloads**: files are written under a temporary name, checked for size and checksum when the provider announces them, retried with back-off, and never exceed the number of connections a service allows.
|
|
20
|
+
- **A manifest** for every download run: what was fetched, from where, when, sizes, SHA-256 checksums, and the provider's licence and attribution. `verify` checks the files against it later.
|
|
21
|
+
- **A provider registry** with access type (none, account, licence), latency, licence, attribution and "last verified" date.
|
|
22
|
+
- **Readers**: NetCDF, HDF5, Zarr, GRIB, GeoTIFF/JPEG 2000/ESRI ASCII, gzip/bzip2/zip containers, and dedicated readers for MSG SEVIRI (native and level-2 GRIB), MTG FCI level-1c, GOES ABI and DWD radar composites.
|
|
23
|
+
- **Maps**: one style registry (`eo-fetch styles`) on top of earthkit-plots; projected data (radar, geostationary) is regridded to latitude/longitude first. NetCDF/Zarr conversion and an earthkit-data bridge.
|
|
24
|
+
- Backends wrap existing libraries (`eumdac`, `earthaccess`, `pystac-client`, `earthkit-data`, `copernicusmarine`, `intake-esgf`, `cdsapi`, `ecmwf-opendata`). eo-fetch does not replace them.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Python 3.12 or 3.13 (the earthkit packages have no wheels for 3.14 yet).
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pip install eo-fetch-api
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
One install includes every provider, the readers and the plots. The package is imported as `eo_fetch` (`import eo_fetch as eof`); the command line tool is called `eo-fetch`. From a clone of the repository (for development): `pip install -e ".[dev]"`. Check the setup:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pytest
|
|
38
|
+
eo-fetch providers
|
|
39
|
+
eo-fetch doctor --provider dwd
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## First five minutes (command line)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
eo-fetch providers # which providers exist
|
|
46
|
+
eo-fetch products precipitation # which product names fit
|
|
47
|
+
eo-fetch search --provider dwd --product radolan-yw --limit 3
|
|
48
|
+
eo-fetch download --provider dwd --product radolan-yw --limit 2 --dest data/dwd
|
|
49
|
+
eo-fetch verify data/dwd/manifest.json --root data/dwd
|
|
50
|
+
eo-fetch open data/dwd/raa01-yw_10000-2611011130-dwd---bin.hdf5
|
|
51
|
+
eo-fetch plot data/dwd/raa01-yw_10000-2611011130-dwd---bin.hdf5 --bbox 5 47 16 55.5
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
All commands: `providers`, `products`, `search`, `download` (`--dry-run` lists only), `verify`, `doctor`, `login`, `collections`, `open`, `plot`, `convert`, `styles`, `check-readers`. Common options of `search` and `download`:
|
|
55
|
+
|
|
56
|
+
| Option | Meaning |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `--provider NAME` | a provider from `eo-fetch providers` |
|
|
59
|
+
| `--product` | a product alias of the card, or the provider's own id |
|
|
60
|
+
| `--start`, `--end` | ISO date or date-time, UTC; `--end` is exclusive |
|
|
61
|
+
| `--bbox W S E N` | area in degrees |
|
|
62
|
+
| `--text` | keyword, glob or id, depending on the provider |
|
|
63
|
+
| `--limit` | at most this many items |
|
|
64
|
+
| `--param KEY=VALUE` | provider-specific option (repeatable); the card's notes list them |
|
|
65
|
+
|
|
66
|
+
## Finding product names
|
|
67
|
+
|
|
68
|
+
`--product` takes either a short alias from the provider card (`radolan-yw`, `imerg-late-daily`, `sentinel-2`, ...) or the provider's own id. You do not have to remember them:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
eo-fetch products # every alias of every provider, with a description
|
|
72
|
+
eo-fetch products precipitation # aliases whose name or description contains the word(s)
|
|
73
|
+
eo-fetch products radar dwd # words may include the provider name
|
|
74
|
+
eo-fetch products --provider dwd # one provider (file trees also list their top-level folders)
|
|
75
|
+
eo-fetch products --provider nasa soil # with --provider and words, the live catalogue is searched too
|
|
76
|
+
eo-fetch products --live "sea surface" # live catalogues of all providers (needs internet/credentials)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Output lines are `provider/product description`; the part after the slash is what you pass to `--product`. Live results are marked `[catalogue]`, folders `[folder]`. Live catalogue search works for `eumetsat`, `nasa`, `planetary-computer`, `earth-search`, `cdse` and `copernicus-marine` (`eo-fetch collections` lists them without aliases). For `esgf` the product is the project (`cmip3`, `cmip5`, `cmip6`, `cmip7`, `cordex-cmip6`, `input4mips`, `obs4mips`) and the facets go into `--param` (facet names differ by project; CMIP6 and CMIP5 are tested, CMIP5 needs `--param indices=all`); for `cds` and `ecmwf-open-data` read the provider notes (`eo-fetch doctor --provider NAME` shows the access details). If a product name is mistyped and nothing is found, `search` and `download` suggest the closest aliases.
|
|
80
|
+
|
|
81
|
+
## Python API
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import eo_fetch as eof
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The API does everything the command line does, with the same names and rules. Options that are `--param KEY=VALUE` on the command line are keyword arguments.
|
|
88
|
+
|
|
89
|
+
### Find product names
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
for match in eof.products("precipitation"): # aliases of all providers
|
|
93
|
+
print(match.provider, match.product, "-", match.description)
|
|
94
|
+
eof.products(provider="dwd") # one provider (+ top-level folders)
|
|
95
|
+
eof.products("soil moisture", "nasa") # with a provider: live catalogue too
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`eof.products(text="", provider=None, *, live=None, browse=None)` returns `eof.ProductMatch` objects (`provider`, `product`, `description`, `source`); pass `match.product` to `eof.search`.
|
|
99
|
+
|
|
100
|
+
### Search
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
found = eof.search(
|
|
104
|
+
"nasa",
|
|
105
|
+
"imerg-late-daily",
|
|
106
|
+
start="2026-10-01",
|
|
107
|
+
end="2026-10-03", # ISO strings, dates or datetimes (end exclusive)
|
|
108
|
+
bbox=(5, 47, 16, 55), # west, south, east, north
|
|
109
|
+
)
|
|
110
|
+
print(len(found), f"{found.megabytes:.1f} MB")
|
|
111
|
+
for item in found:
|
|
112
|
+
print(item.time, item.key, item.size)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`eof.search(provider, product="", *, start, end, bbox, text, limit, params, **options)` returns `eof.Results`, a normal list of `eof.Item` that also remembers how to download itself. Provider options go in as keywords or in a `params` dict; lists become comma-separated values:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
eof.search(
|
|
119
|
+
"cdse",
|
|
120
|
+
"sentinel-2",
|
|
121
|
+
start="2026-10-28",
|
|
122
|
+
end="2026-10-30",
|
|
123
|
+
bbox=(8.5, 53.4, 9.5, 53.8),
|
|
124
|
+
assets=["B04_60m", "SCL_60m"],
|
|
125
|
+
limit=2,
|
|
126
|
+
)
|
|
127
|
+
eof.search(
|
|
128
|
+
"esgf",
|
|
129
|
+
"CMIP6",
|
|
130
|
+
variable_id="tas",
|
|
131
|
+
experiment_id="historical",
|
|
132
|
+
source_id="CanESM5",
|
|
133
|
+
member_id="r1i1p1f1",
|
|
134
|
+
table_id="Amon",
|
|
135
|
+
)
|
|
136
|
+
eof.search(
|
|
137
|
+
"cds",
|
|
138
|
+
"era5-single-levels",
|
|
139
|
+
start="2026-01-01",
|
|
140
|
+
end="2026-02-01",
|
|
141
|
+
variable="2m_temperature",
|
|
142
|
+
bbox=(5, 47, 16, 55),
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
An unknown option raises `ValueError` with the accepted names. Filter or slice the results freely (`found[:3]`, `found.where(lambda item: item.size < 5e6)`); they keep working with `eof.download`.
|
|
147
|
+
|
|
148
|
+
### Download and verify
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
results = eof.download(found, "data/imerg") # also writes data/imerg/manifest.json
|
|
152
|
+
failed = [r for r in results if r.status == "failed"] # downloaded | skipped | failed
|
|
153
|
+
problems = eof.verify("data/imerg/manifest.json") # [] when every file matches
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`eof.download(results, dest, *, workers=None, retries=3, manifest=None, on_result=None)` returns a list of `eof.Result` (`item`, `path`, `status`, `size`, `sha256`, `seconds`, `error`). Failures do not raise. Files that already exist with the right size and checksum are skipped, so the same call resumes an interrupted run. `on_result` is called as each file finishes (for progress output).
|
|
157
|
+
|
|
158
|
+
### Open, plot, convert
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
ds = eof.open(results[0].path) # path of the first downloaded file
|
|
162
|
+
path = eof.plot(ds, "plots/imerg.png", bbox=(5, 47, 16, 55), style="precip-daily")
|
|
163
|
+
eof.convert("data/era5/era5.grib", "data/era5/era5.nc") # or .zarr
|
|
164
|
+
ekd = eof.to_earthkit("data/era5/era5.grib") # earthkit-data object
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`eof.open(path, format=None, **options)` returns an `xarray.Dataset`; the format is detected from the name and content (see below). `eof.plot(source, output=None, *, variable, style, domain, resolution, bbox, title)` accepts a file or a Dataset and returns the image path. See `eo-fetch styles` for style names.
|
|
168
|
+
|
|
169
|
+
### Providers, credentials, diagnostics
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
for info in eof.providers(): # ProviderInfo cards
|
|
173
|
+
print(info.name, info.auth, info.verified)
|
|
174
|
+
eof.provider("cds").notes # how to use the provider, options, limits
|
|
175
|
+
eof.collections("copernicus-marine", "sea surface temperature")
|
|
176
|
+
eof.login("cds") # hidden prompts; saves to your user folder
|
|
177
|
+
eof.doctor("nasa") # prints what is missing; True when all is well
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Lower-level pieces (`eo_fetch.backends`, `eo_fetch.readers`, `eo_fetch.regrid`) are importable too and are what the functions above are built on. To add a provider, implement the `Backend` protocol (`search(query)`, `fetch(item, target)`, `max_connections`) and register a `ProviderInfo` card.
|
|
181
|
+
|
|
182
|
+
## Providers
|
|
183
|
+
|
|
184
|
+
`eo-fetch providers` always shows the current list and verification dates.
|
|
185
|
+
|
|
186
|
+
| Provider | What | Access |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| `eumetsat` | EUMETSAT Data Store: Meteosat, MTG, Metop, Sentinel-3/5P/6, CM SAF (all collections) | licence per collection |
|
|
189
|
+
| `noaa` | GOES-R (16, 18, 19) and other public NOAA buckets on AWS | none |
|
|
190
|
+
| `dwd` | DWD open data tree: radar, ICON models, climate data centre | none |
|
|
191
|
+
| `nasa` | every collection of NASA's CMR (IMERG, MERRA-2, OMI, SMAP, ...) | free Earthdata account |
|
|
192
|
+
| `planetary-computer`, `earth-search` | STAC catalogues: Sentinel, Landsat, land cover, climate | none |
|
|
193
|
+
| `cdse` | Copernicus Data Space (Sentinel) via STAC and S3 | free account + S3 keys |
|
|
194
|
+
| `ecmwf-open-data` | IFS and ensemble forecasts by parameter and lead time | none |
|
|
195
|
+
| `cds` | ERA5, ERA5-Land and other Climate Data Store datasets | free account, licence per dataset |
|
|
196
|
+
| `copernicus-marine` | ocean physics, biogeochemistry, sea ice, waves | free account |
|
|
197
|
+
| `esgf` | CMIP climate-model output (CMIP5 and CMIP6 tested; CMIP3/7, CORDEX-CMIP6, obs4MIPs as far as the indices serve them) | none |
|
|
198
|
+
|
|
199
|
+
Each card (`eof.provider(name)` or `eo-fetch doctor --provider NAME`) lists licence, attribution, latency and the options of that provider. Respect the licence and attribution when you publish results; the manifest records both.
|
|
200
|
+
|
|
201
|
+
## Credentials
|
|
202
|
+
|
|
203
|
+
Providers with accounts have a one-time `eo-fetch login --provider NAME` (hidden prompts; the login is saved as a plain-text file in your user folder, and deleting the file logs you out). `eo-fetch doctor --provider NAME` tells you what is missing. You can also set the environment variables, which take priority, or write the file yourself. eo-fetch never stores credentials anywhere else, never writes them into manifests or download folders, and sends them only to the provider.
|
|
204
|
+
|
|
205
|
+
| Provider | `login` writes | Or use |
|
|
206
|
+
| --- | --- | --- |
|
|
207
|
+
| `nasa` (Earthdata) | `~/.netrc` | `EARTHDATA_USERNAME` and `EARTHDATA_PASSWORD`, or `EARTHDATA_TOKEN`; approve the provider's application once at urs.earthdata.nasa.gov |
|
|
208
|
+
| `eumetsat` | `~/.eumdac/credentials` | `EUMETSAT_KEY` and `EUMETSAT_SECRET`; a licence per collection |
|
|
209
|
+
| `cds` (ERA5) | `~/.cdsapirc` | `CDSAPI_URL` and `CDSAPI_KEY`; accept each dataset's licence on its page |
|
|
210
|
+
| `copernicus-marine` | `~/.copernicusmarine/.copernicusmarine-credentials` (written by the toolbox) | `COPERNICUSMARINE_SERVICE_USERNAME` and `COPERNICUSMARINE_SERVICE_PASSWORD` |
|
|
211
|
+
| `cdse` | `~/.eo-fetch/cdse` (`key,secret`) | `CDSE_S3_KEY` and `CDSE_S3_SECRET` from the Copernicus Data Space S3 key manager (searching needs no keys) |
|
|
212
|
+
| `dwd`, `noaa`, `earth-search`, `planetary-computer`, `ecmwf-open-data`, `esgf` | none needed | |
|
|
213
|
+
|
|
214
|
+
## Which files can be opened?
|
|
215
|
+
|
|
216
|
+
`eo-fetch check-readers` downloads the smallest recent file of every product alias of every provider and tries to open it, and prints ok, no reader, fail or skip for each. In the last full run: all GOES-18/19 products, SEVIRI (L1.5, IODC, RSS, cloud mask, cloud top height), FCI level 1c and cloud mask, MTG precipitation and lightning, CM SAF products, DWD radar composites, ICON-EU, DWD satellite radiation and NOAA GFS GRIB opened. Sentinel-2 JPEG 2000, ERA5 GRIB/NetCDF, ECMWF open data, Copernicus Marine and CMIP6 NetCDF were opened in the provider tests.
|
|
217
|
+
|
|
218
|
+
Known limits:
|
|
219
|
+
|
|
220
|
+
- Not readable yet: BUFR (ASCAT, RAP), DWD station zips and radar `.tar` composites, NEXRAD.
|
|
221
|
+
- ICON icosahedral grids open only as a flat list of values (they need DWD's grid files for coordinates).
|
|
222
|
+
- OMI and SMAP HDF5 files open but are not plotted (2-D latitude/longitude arrays).
|
|
223
|
+
- Plots draw the first entry of extra dimensions (time, level, band). Categorical rasters such as Sentinel-2 `SCL` have no dedicated style yet.
|
|
224
|
+
- CMIP6 searches may warn about a publishing error in the new STAC indices of ESGF; the warning is from `intake-esgf`. Switch those indices off with `--param indices=-discovery.east.esgf.io,-discovery.west.esgf.io`. Older projects (CMIP5, CMIP3) are only in the legacy indices: add `--param indices=all`; some legacy nodes may be offline and are reported as warnings.
|
|
225
|
+
- On Windows, paths are limited to 260 characters; use a short `--dest`. A harmless `Error in sys.excepthook` message can appear at exit after reading GeoTIFF/JPEG 2000 files.
|
|
226
|
+
|
|
227
|
+
## Docker
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
docker build -t eo-fetch .
|
|
231
|
+
docker run --rm eo-fetch providers
|
|
232
|
+
docker run --rm -v "${PWD}/data:/data" eo-fetch download --provider dwd --product radolan-yw --limit 2 --dest /data/dwd
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
(`${PWD}` is PowerShell; use `$(pwd)` in bash.) The image contains no credentials. For providers with accounts, either pass the environment variables from the table above (`-e EARTHDATA_USERNAME -e EARTHDATA_PASSWORD`, ...) or mount the credential file read-only, for example `-v "$HOME/.cdsapirc:/home/eo/.cdsapirc:ro"`.
|
|
236
|
+
## Roadmap
|
|
237
|
+
|
|
238
|
+
Version 2 providers: CHIRPS and other precipitation products, river flow, agriculture and land use, soil moisture. Optional: GRIB subsetting through `herbie`, a `wetterdienst` backend for DWD station data, readers for BUFR and DWD station zips.
|
|
239
|
+
|
|
240
|
+
## Development
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
ruff format .
|
|
244
|
+
ruff check .
|
|
245
|
+
pytest -m "not network and not account"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Tests that need the internet or a provider account are marked `network` or `account` and are skipped in CI.
|