ro-climate 0.1.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.
- ro_climate-0.1.0/CHANGELOG.md +22 -0
- ro_climate-0.1.0/LICENSE +21 -0
- ro_climate-0.1.0/MANIFEST.in +4 -0
- ro_climate-0.1.0/PKG-INFO +539 -0
- ro_climate-0.1.0/README.md +499 -0
- ro_climate-0.1.0/pyproject.toml +78 -0
- ro_climate-0.1.0/ro_climate/__init__.py +62 -0
- ro_climate-0.1.0/ro_climate/_chunk_cache.py +369 -0
- ro_climate-0.1.0/ro_climate/_reduce.py +103 -0
- ro_climate-0.1.0/ro_climate/_store.py +123 -0
- ro_climate-0.1.0/ro_climate/archive.py +75 -0
- ro_climate-0.1.0/ro_climate/area.py +898 -0
- ro_climate-0.1.0/ro_climate/client.py +904 -0
- ro_climate-0.1.0/ro_climate/exceptions.py +29 -0
- ro_climate-0.1.0/ro_climate/grid.py +262 -0
- ro_climate-0.1.0/ro_climate/indices.py +64 -0
- ro_climate-0.1.0/ro_climate/point.py +765 -0
- ro_climate-0.1.0/ro_climate/py.typed +0 -0
- ro_climate-0.1.0/ro_climate/variables.py +73 -0
- ro_climate-0.1.0/ro_climate.egg-info/PKG-INFO +539 -0
- ro_climate-0.1.0/ro_climate.egg-info/SOURCES.txt +30 -0
- ro_climate-0.1.0/ro_climate.egg-info/dependency_links.txt +1 -0
- ro_climate-0.1.0/ro_climate.egg-info/requires.txt +12 -0
- ro_climate-0.1.0/ro_climate.egg-info/top_level.txt +1 -0
- ro_climate-0.1.0/setup.cfg +4 -0
- ro_climate-0.1.0/tests/test_archive.py +48 -0
- ro_climate-0.1.0/tests/test_area.py +839 -0
- ro_climate-0.1.0/tests/test_chunk_cache.py +290 -0
- ro_climate-0.1.0/tests/test_client.py +254 -0
- ro_climate-0.1.0/tests/test_grid.py +148 -0
- ro_climate-0.1.0/tests/test_point.py +244 -0
- ro_climate-0.1.0/tests/test_store.py +133 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
|
+
and the project uses [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-11-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Point time series and area summaries from the ANM daily gridded
|
|
13
|
+
meteorological cube for Romania.
|
|
14
|
+
- `RoClimate`, `RoClimatePoint`, and `RoClimateArea` public API.
|
|
15
|
+
- Statistics, resampling, climatologies, climate indices, and narrative
|
|
16
|
+
climate-description helpers.
|
|
17
|
+
- Chunk-granular area reads and a persistent raw Zarr chunk cache.
|
|
18
|
+
- CSV, JSON, NPZ, and zip exports.
|
|
19
|
+
- Offline synthetic test suite.
|
|
20
|
+
|
|
21
|
+
[Unreleased]: https://github.com/strainu/ro_climate/compare/v0.1.0...HEAD
|
|
22
|
+
[0.1.0]: https://github.com/strainu/ro_climate/releases/tag/v0.1.0
|
ro_climate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Strainu and ro-climate contributors
|
|
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,539 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ro-climate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Romanian daily gridded climate data from ANM
|
|
5
|
+
Author-email: Strainu <wiki@strainu.ro>
|
|
6
|
+
Maintainer-email: Strainu <wiki@strainu.ro>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/strainu/ro_climate
|
|
9
|
+
Project-URL: Documentation, https://github.com/strainu/ro_climate#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/strainu/ro_climate
|
|
11
|
+
Project-URL: Issues, https://github.com/strainu/ro_climate/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/strainu/ro_climate/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: climate,meteorology,romania,anm,weather,gridded-data,temperature,precipitation,zarr
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: numpy>=1.23
|
|
30
|
+
Requires-Dist: pandas>=2.0
|
|
31
|
+
Requires-Dist: requests>=2.28
|
|
32
|
+
Requires-Dist: zarr<3,>=2.18
|
|
33
|
+
Provides-Extra: test
|
|
34
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
37
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
38
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# ro-climate — ANM daily gridded meteorology
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/ro-climate/)
|
|
44
|
+
[](https://pypi.org/project/ro-climate/)
|
|
45
|
+
[](https://github.com/strainu/ro_climate/actions/workflows/ci.yml)
|
|
46
|
+
[](https://github.com/strainu/ro_climate/blob/main/LICENSE)
|
|
47
|
+
|
|
48
|
+
`ro-climate` is a small, object-oriented Python client for the public **ANM
|
|
49
|
+
daily gridded meteorological cube** for Romania. It reads single grid cells or
|
|
50
|
+
rectangular/polygon areas for an optional date range, returns pandas time
|
|
51
|
+
series or daily spatial summaries, and provides statistics, time aggregation,
|
|
52
|
+
climate indices, exports, and a local chunk cache.
|
|
53
|
+
|
|
54
|
+
- Source code: <https://github.com/strainu/ro_climate>
|
|
55
|
+
- Issue tracker: <https://github.com/strainu/ro_climate/issues>
|
|
56
|
+
- Changelog: [CHANGELOG.md](https://github.com/strainu/ro_climate/blob/main/CHANGELOG.md)
|
|
57
|
+
- Store: <https://storage.svc.uvt-01.eocube.ro/meteo/cube/open-v1/>
|
|
58
|
+
- STAC item: <https://stac.eocube.ro/collections/anm-daily-grids/items/anm-daily-grids-open-v1>
|
|
59
|
+
- Variables: `max_temp` (°C), `min_temp` (°C), `precipitation` (mm)
|
|
60
|
+
- Data licence: CC BY 4.0
|
|
61
|
+
|
|
62
|
+
> **Data attribution.** The data are published by Administrația Națională de
|
|
63
|
+
> Meteorologie (MeteoRomania), via data.gov.ro, under CC BY 4.0; cube assembly
|
|
64
|
+
> and hosting are by West University of Timișoara (ROCS / EOCube.RO). Any
|
|
65
|
+
> publication using the data must preserve this attribution.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
`ro-climate` requires Python 3.10 or newer.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python -m pip install ro-climate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For development, install the test and release tools as well:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m pip install -e ".[dev]" # pytest, build, twine
|
|
79
|
+
python -m pip install -e ".[test]" # pytest only
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Runtime dependencies are deliberately small: `zarr` (v2), `numpy`, `pandas`,
|
|
83
|
+
and `requests`. No `xarray`, `fsspec`, `aiohttp`, or plotting stack is needed.
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from ro_climate import RoClimate
|
|
89
|
+
|
|
90
|
+
with RoClimate() as client:
|
|
91
|
+
point = client.point(
|
|
92
|
+
26.1025,
|
|
93
|
+
44.4325,
|
|
94
|
+
start="2020-01-01",
|
|
95
|
+
end="2020-12-31",
|
|
96
|
+
)
|
|
97
|
+
print(point)
|
|
98
|
+
print(point.mean()) # mean of each variable
|
|
99
|
+
print(point.max("max_temp")) # hottest day in the period
|
|
100
|
+
print(point.sum("precipitation")) # total precipitation
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **Performance note.** The cube stores 90-day × 161-row × 324-column chunks,
|
|
104
|
+
> so a single point's full 1961–present history requires downloading the
|
|
105
|
+
> chunks covering that point for every 90-day period. The first uncached
|
|
106
|
+
> full-history request can therefore transfer several gigabytes and take
|
|
107
|
+
> minutes per variable. For interactive work, pass a narrower `start`/`end`
|
|
108
|
+
> and select only the variables you need. Area requests are chunk-aligned and
|
|
109
|
+
> read each intersecting chunk once instead of repeating a point download for
|
|
110
|
+
> every cell. Point series and area summaries are not archived; the raw Zarr
|
|
111
|
+
> chunk cache (enabled by default) makes later overlapping requests local.
|
|
112
|
+
|
|
113
|
+
The requested coordinate is snapped to the containing 0.01° grid cell. The
|
|
114
|
+
result records the cell indices, the cell centre, and the cell bounds:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
print(point.i, point.j)
|
|
118
|
+
print(point.grid_lon, point.grid_lat)
|
|
119
|
+
print(point.cell_bounds) # (lon_min, lon_max, lat_min, lat_max)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The cube uses a 0.01° grid with cell centres from 20.135°E to 29.845°E and
|
|
123
|
+
48.355°N to 43.535°N. Coordinates outside the grid raise `OutsideGridError`.
|
|
124
|
+
|
|
125
|
+
## Date ranges and frontiers
|
|
126
|
+
|
|
127
|
+
- Without `start`, a point starts at the first cube day, **1961-01-01**.
|
|
128
|
+
- Without `end`, it ends at the latest day that is available for **all**
|
|
129
|
+
requested variables.
|
|
130
|
+
- Variable frontiers are read from the authoritative `last_*_time` arrays,
|
|
131
|
+
not from the STAC `end_datetime` field, which can lag behind the data.
|
|
132
|
+
- An `end` later than the common frontier is clamped and reported in
|
|
133
|
+
`point.warnings`. Intersecting frontiers avoids returning the zero-filled
|
|
134
|
+
preallocated days beyond each variable's last written value.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
point = client.point(26.1025, 44.4325, start="2015-06-01")
|
|
138
|
+
print(point.start, point.end, point.warnings)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Statistics
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
point.mean() # pandas Series, one value per loaded variable
|
|
145
|
+
point.avg() # alias of mean()
|
|
146
|
+
point.max()
|
|
147
|
+
point.min()
|
|
148
|
+
point.median()
|
|
149
|
+
point.std()
|
|
150
|
+
point.var()
|
|
151
|
+
point.quantile(0.9)
|
|
152
|
+
point.count()
|
|
153
|
+
point.describe() # count/mean/std/min/... plus missing and sum
|
|
154
|
+
point.coverage # valid/missing counts and fraction per variable
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`sum()` is only defined for additive variables. It defaults to precipitation
|
|
158
|
+
and raises `NonAdditiveVariableError` for temperature:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
point.sum() # precipitation total
|
|
162
|
+
point.sum("precipitation")
|
|
163
|
+
point.sum("max_temp") # raises NonAdditiveVariableError
|
|
164
|
+
point.sum("max_temp", allow_non_additive=True) # explicit override
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
All statistics skip missing values by default (`skipna=True`).
|
|
168
|
+
|
|
169
|
+
## Time aggregation
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
point.monthly_mean() # month-start index
|
|
173
|
+
point.monthly_total() # precipitation totals
|
|
174
|
+
point.annual_mean()
|
|
175
|
+
point.annual_total()
|
|
176
|
+
point.annual_extremes() # yearly max and min for each variable
|
|
177
|
+
point.extreme_dates() # min/max values and their dates
|
|
178
|
+
point.annual_mean_temperature() # yearly means of (max_temp + min_temp) / 2
|
|
179
|
+
point.monthly_mean_temperature() # climatological monthly means
|
|
180
|
+
point.warmest_month() # (month, mean temperature)
|
|
181
|
+
point.coldest_month() # (month, mean temperature)
|
|
182
|
+
point.wettest_year() # (year, annual precipitation)
|
|
183
|
+
point.driest_year() # (year, annual precipitation)
|
|
184
|
+
point.precipitation_summary() # mean, wettest and driest year
|
|
185
|
+
point.climatology("month") # mean by calendar month
|
|
186
|
+
point.climatology("dayofyear") # mean by day of year
|
|
187
|
+
|
|
188
|
+
point.resample("MS", "mean") # any pandas offset alias
|
|
189
|
+
point.resample("YS", "max")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`resample(..., how="sum")` is rejected for temperature, just like `sum()`.
|
|
193
|
+
|
|
194
|
+
### Filtered queries in one line
|
|
195
|
+
|
|
196
|
+
`RoClimatePoint.aggregate()` combines date filtering, month/season filtering, and a
|
|
197
|
+
reduction. For example, the maximum temperature in January over a date range:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
point = client.point(26.1025, 44.4325, start="2010-01-01", end="2020-12-31")
|
|
201
|
+
jan_max = point.aggregate("max_temp", "max", months=1)
|
|
202
|
+
print(jan_max)
|
|
203
|
+
|
|
204
|
+
# Same query, one value per year
|
|
205
|
+
per_year = point.aggregate("max_temp", "max", months=1, groupby="year")
|
|
206
|
+
|
|
207
|
+
# Seasons: DJF, MAM, JJA, SON
|
|
208
|
+
jja_mean = point.aggregate("max_temp", "mean", season="JJA")
|
|
209
|
+
winter = point.aggregate(
|
|
210
|
+
"precipitation", "sum", season="DJF", groupby="season_year"
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`groupby` accepts `"year"`, `"month"`, `"season_year"`, or a pandas offset
|
|
215
|
+
alias such as `"YS"` and `"MS"`. `season_year` assigns December to the
|
|
216
|
+
following winter.
|
|
217
|
+
|
|
218
|
+
To keep several variables or apply custom pandas logic, use `subset()`:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
jan = point.subset(start="2020-01-01", end="2020-01-31", months=1)
|
|
222
|
+
print(jan.data)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Derived values and climate indices
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
point.tmean # (max_temp + min_temp) / 2
|
|
229
|
+
point.diurnal_range # max_temp - min_temp
|
|
230
|
+
point.mean_temperature()
|
|
231
|
+
point.mean_diurnal_range()
|
|
232
|
+
|
|
233
|
+
point.frost_days() # min_temp < 0 °C
|
|
234
|
+
point.summer_days() # max_temp >= 25 °C
|
|
235
|
+
point.hot_days() # max_temp >= 30 °C
|
|
236
|
+
point.tropical_nights() # min_temp >= 20 °C
|
|
237
|
+
point.rain_days() # precipitation >= 1 mm
|
|
238
|
+
point.heavy_rain_days() # precipitation >= 10 mm
|
|
239
|
+
point.dry_days() # precipitation < 1 mm
|
|
240
|
+
point.max_daily_precip()
|
|
241
|
+
point.max_n_day_precip(5) # max rolling 5-day precipitation total
|
|
242
|
+
point.growing_degree_days(10) # base 10 °C, mean-temperature method
|
|
243
|
+
point.consecutive_dry_days()
|
|
244
|
+
point.consecutive_wet_days()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Most threshold indices accept a custom `threshold` and a `by` argument:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
point.hot_days(30, by="year")
|
|
251
|
+
point.rain_days(1.0, by="month")
|
|
252
|
+
point.frost_days(0.0, by="season_year")
|
|
253
|
+
point.hot_days(30, by="YS") # any pandas offset alias
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Climate description helpers
|
|
257
|
+
|
|
258
|
+
These return structured values for narrative summaries:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
point.extreme_dates(["min_temp", "max_temp"])
|
|
262
|
+
# min min_date max max_date
|
|
263
|
+
# min_temp ... ... ... ...
|
|
264
|
+
# max_temp ... ... ... ...
|
|
265
|
+
|
|
266
|
+
point.annual_mean_temperature() # yearly means of (max_temp + min_temp) / 2
|
|
267
|
+
point.monthly_mean_temperature() # climatological monthly means
|
|
268
|
+
point.warmest_month(), point.coldest_month()
|
|
269
|
+
point.precipitation_summary()
|
|
270
|
+
# {'mean_annual': ..., 'wettest': (year, value), 'driest': (year, value)}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`extreme_dates()` reports the value and date of the minimum and maximum. For a
|
|
274
|
+
point, the index is the variable; values are computed over the loaded period.
|
|
275
|
+
|
|
276
|
+
These helpers operate on the existing summary series. Spatial ranges across a
|
|
277
|
+
county require per-cell aggregation and are not provided by the area object.
|
|
278
|
+
|
|
279
|
+
## Area and grid summaries
|
|
280
|
+
|
|
281
|
+
`area()` reads a rectangular window and returns **daily spatial summaries**,
|
|
282
|
+
not raw grids. Bounds are `(west, south, east, north)` in WGS84 degrees:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
area = client.area(
|
|
286
|
+
bounds=(25.90, 44.30, 26.30, 44.60),
|
|
287
|
+
start="2020-01-01",
|
|
288
|
+
end="2020-12-31",
|
|
289
|
+
variables=["max_temp", "precipitation"],
|
|
290
|
+
reductions=["mean", "max"],
|
|
291
|
+
)
|
|
292
|
+
print(area.data.head())
|
|
293
|
+
print(area.mean()) # temporal mean of each daily summary
|
|
294
|
+
print(area.series("max_temp", "max"))
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Supported spatial reductions are `mean` (the default), `min`, `max`, `sum`,
|
|
298
|
+
and `count`. Data columns are named `{variable}_{reduction}`, for example
|
|
299
|
+
`max_temp_mean` or `precipitation_count`.
|
|
300
|
+
|
|
301
|
+
Pass `polygon=` to keep only cells whose centres fall inside a simple polygon.
|
|
302
|
+
The polygon's bounding box is used as the search window unless `bounds=` is
|
|
303
|
+
also given, in which case the polygon acts as a mask within the window:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
area = client.area(
|
|
307
|
+
polygon=[
|
|
308
|
+
(26.00, 44.30),
|
|
309
|
+
(26.30, 44.30),
|
|
310
|
+
(26.30, 44.60),
|
|
311
|
+
(26.00, 44.60),
|
|
312
|
+
],
|
|
313
|
+
start="2020-06-01",
|
|
314
|
+
end="2020-08-31",
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Holes and multipart polygons are not supported. Selection is by cell centre;
|
|
319
|
+
there is no exact area weighting.
|
|
320
|
+
|
|
321
|
+
`min_count` is the minimum number of valid cells required for a non-count
|
|
322
|
+
reduction (default `1`). Spatial `sum` is limited to additive variables
|
|
323
|
+
(precipitation) unless `allow_non_additive=True` is passed:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
dry_cells = client.area(
|
|
327
|
+
bounds=(25.90, 44.30, 26.30, 44.60),
|
|
328
|
+
start="2020-01-01",
|
|
329
|
+
reductions=["mean", "count"],
|
|
330
|
+
min_count=10,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
rain = client.area(
|
|
334
|
+
bounds=(25.90, 44.30, 26.30, 44.60),
|
|
335
|
+
start="2020-01-01",
|
|
336
|
+
variables="precipitation",
|
|
337
|
+
reductions="sum",
|
|
338
|
+
)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The returned `RoClimateArea` exposes:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
area.data # daily summaries, one row per day
|
|
345
|
+
area.bounds # requested west/south/east/north
|
|
346
|
+
area.i0, area.i1 # inclusive column indices
|
|
347
|
+
area.j0, area.j1 # inclusive row indices
|
|
348
|
+
area.n_cells # selected cells (after polygon masking)
|
|
349
|
+
area.shape # (rows, columns) of the grid window
|
|
350
|
+
area.variables, area.reductions, area.columns
|
|
351
|
+
area.coverage # valid cell-days per variable
|
|
352
|
+
area.warnings # clipping and clamping notices
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`.coverage` covers the requested date range and the selected cells.
|
|
356
|
+
|
|
357
|
+
It offers the same style of access and aggregation as a point:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
area.at("2020-07-01")
|
|
361
|
+
area.subset(start="2020-06-01", end="2020-08-31", months=[6, 7, 8])
|
|
362
|
+
area["max_temp_mean"]
|
|
363
|
+
area[["max_temp_mean", "precipitation_mean"]]
|
|
364
|
+
area.mean(), area.max(), area.min(), area.median()
|
|
365
|
+
area.std(), area.var(), area.count(), area.quantile(0.9)
|
|
366
|
+
area.sum(), area.describe()
|
|
367
|
+
|
|
368
|
+
area.resample("MS", "mean")
|
|
369
|
+
area.monthly_mean(), area.monthly_total()
|
|
370
|
+
area.annual_mean(), area.annual_total()
|
|
371
|
+
area.annual_extremes()
|
|
372
|
+
area.extreme_dates()
|
|
373
|
+
area.climatology("month"), area.climatology("dayofyear")
|
|
374
|
+
area.aggregate("max_temp", "max", how="mean", months=1, groupby="year")
|
|
375
|
+
|
|
376
|
+
area.tmean # daily spatial mean temperature
|
|
377
|
+
area.mean_temperature()
|
|
378
|
+
area.annual_mean_temperature()
|
|
379
|
+
area.monthly_mean_temperature()
|
|
380
|
+
area.warmest_month(), area.coldest_month()
|
|
381
|
+
area.wettest_year(), area.driest_year()
|
|
382
|
+
area.precipitation_summary()
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`area.tmean` is `(max_temp_mean + min_temp_mean) / 2`, so both temperature
|
|
386
|
+
variables must be loaded with the default `mean` reduction.
|
|
387
|
+
|
|
388
|
+
### Area reads
|
|
389
|
+
|
|
390
|
+
Area summaries are not stored. Each call reads the chunks intersecting the
|
|
391
|
+
selected cells and computes the requested reductions.
|
|
392
|
+
|
|
393
|
+
## Local chunk cache
|
|
394
|
+
|
|
395
|
+
The only cache is the raw compressed Zarr chunk cache, enabled by default:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
client = RoClimate(
|
|
399
|
+
chunk_cache_dir="ro-climate-cache",
|
|
400
|
+
chunk_cache_max_mb=2048,
|
|
401
|
+
)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Compressed Zarr chunks are stored under
|
|
405
|
+
`<chunk_cache_dir>/chunks/<store digest>/`. They are shared by point and area
|
|
406
|
+
requests. The cache is capped at **20 GB** by default. Set
|
|
407
|
+
`chunk_cache_max_mb=` or the `RO_CLIMATE_CHUNK_CACHE_MAX_MB` environment
|
|
408
|
+
variable to change it; `0` disables the limit. The oldest chunks are evicted
|
|
409
|
+
when the cap is exceeded.
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
from ro_climate import RoClimate
|
|
413
|
+
|
|
414
|
+
client = RoClimate(chunk_cache_dir="ro-climate-cache")
|
|
415
|
+
point = client.point(26.1025, 44.4325, start="2020-01-01", end="2020-12-31")
|
|
416
|
+
area = client.area(
|
|
417
|
+
bounds=(25.0, 44.0, 27.0, 45.0), start="2020-01-01", end="2020-12-31"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
print(client.chunk_cache_info())
|
|
421
|
+
client.prune_cache(older_than_days=30)
|
|
422
|
+
client.clear_cache()
|
|
423
|
+
client.close()
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Set `RO_CLIMATE_CACHE_DIR` to override the platform cache root and
|
|
427
|
+
`RO_CLIMATE_CHUNK_CACHE_MAX_MB` to override the size cap. Pass
|
|
428
|
+
`chunk_cache=False` to disable local storage, or `refresh=True` to a `point()`
|
|
429
|
+
or `area()` call to invalidate the chunks covering the requested range.
|
|
430
|
+
|
|
431
|
+
## Exports
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
point.to_csv("bucharest_2020.csv")
|
|
435
|
+
point.to_json("bucharest_2020.json")
|
|
436
|
+
point.to_npz("bucharest_2020.npz")
|
|
437
|
+
point.to_zip("bucharest_2020.zip") # same archive layout
|
|
438
|
+
print(point.metadata())
|
|
439
|
+
|
|
440
|
+
area.to_csv("muntenia_2020.csv")
|
|
441
|
+
area.to_json("muntenia_2020.json")
|
|
442
|
+
area.to_npz("muntenia_2020.npz")
|
|
443
|
+
area.to_zip("muntenia_2020.zip")
|
|
444
|
+
print(area.metadata())
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`to_csv()` writes the date index with the column name `date` and formats
|
|
448
|
+
floats to four decimals by default. Extra keyword arguments are passed to
|
|
449
|
+
pandas, so override `float_format` or use `sep=";"` as needed.
|
|
450
|
+
|
|
451
|
+
## API summary
|
|
452
|
+
|
|
453
|
+
`RoClimate(...)`
|
|
454
|
+
|
|
455
|
+
- `RoClimate(store_url=None, *, discover=True, chunk_cache=True,
|
|
456
|
+
chunk_cache_dir=None, chunk_cache_max_mb=None, timeout=30.0, retries=3,
|
|
457
|
+
frontier_ttl=3600)`
|
|
458
|
+
- `.point(lon, lat, start=None, end=None, variables=None, *, refresh=None,
|
|
459
|
+
strict=False) -> RoClimatePoint`
|
|
460
|
+
- `.area(bounds=None, start=None, end=None, variables=None, reductions=None,
|
|
461
|
+
*, polygon=None, min_count=1, allow_non_additive=False, refresh=None,
|
|
462
|
+
strict=False) -> RoClimateArea`
|
|
463
|
+
- `.frontiers(force=False)` — last written day per variable
|
|
464
|
+
- `.chunk_cache_info()`, `.clear_cache()`, `.prune_cache(older_than_days=30)`
|
|
465
|
+
- Context manager and `.close()`
|
|
466
|
+
|
|
467
|
+
`RoClimatePoint`
|
|
468
|
+
|
|
469
|
+
- Access: `.data`, `.variables`, `.units`, `.start`, `.end`, `.i`, `.j`,
|
|
470
|
+
`.grid_lon`, `.grid_lat`, `.cell_bounds`, `.coverage`, `.warnings`
|
|
471
|
+
- Selection: `.at(date)`, `.subset(...)`, `point["max_temp"]`,
|
|
472
|
+
`point[["max_temp", "precipitation"]]`
|
|
473
|
+
- Statistics: `.mean/.avg/.max/.min/.median/.std/.var/.quantile/.count/
|
|
474
|
+
.sum/.describe`
|
|
475
|
+
- Aggregation: `.resample`, `.monthly_mean`, `.monthly_total`,
|
|
476
|
+
`.annual_mean`, `.annual_total`, `.annual_extremes`, `.extreme_dates`,
|
|
477
|
+
`.annual_mean_temperature`, `.monthly_mean_temperature`,
|
|
478
|
+
`.warmest_month`, `.coldest_month`, `.wettest_year`, `.driest_year`,
|
|
479
|
+
`.precipitation_summary`, `.climatology`, `.aggregate`
|
|
480
|
+
- Indices and derived values as listed above
|
|
481
|
+
- Exports: `.to_csv`, `.to_json`, `.to_npz`, `.to_zip`, `.metadata`
|
|
482
|
+
|
|
483
|
+
`RoClimateArea`
|
|
484
|
+
|
|
485
|
+
- Access: `.data`, `.bounds`, `.i0/.i1/.j0/.j1`, `.shape`, `.n_cells`,
|
|
486
|
+
`.variables`, `.reductions`, `.columns`, `.polygon`, `.coverage`,
|
|
487
|
+
`.warnings`, `.tmean`
|
|
488
|
+
- Selection: `.at(date)`, `.subset(...)`, `.series(variable, reduction)`,
|
|
489
|
+
`area["max_temp_mean"]`, `area[["max_temp_mean", "max_temp_max"]]`
|
|
490
|
+
- Statistics over daily spatial summaries: `.mean/.avg/.max/.min/.median/
|
|
491
|
+
.std/.var/.quantile/.count/.sum/.describe`, `.mean_temperature`
|
|
492
|
+
- Aggregation: `.resample`, `.monthly_mean`, `.monthly_total`,
|
|
493
|
+
`.annual_mean`, `.annual_total`, `.annual_extremes`, `.extreme_dates`,
|
|
494
|
+
`.annual_mean_temperature`, `.monthly_mean_temperature`,
|
|
495
|
+
`.warmest_month`, `.coldest_month`, `.wettest_year`, `.driest_year`,
|
|
496
|
+
`.precipitation_summary`, `.climatology`, `.aggregate`
|
|
497
|
+
- Exports: `.to_csv`, `.to_json`, `.to_npz`, `.to_zip`, `.metadata`
|
|
498
|
+
|
|
499
|
+
## Tests
|
|
500
|
+
|
|
501
|
+
From this directory:
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
python -m pytest
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
The tests use a synthetic in-memory group and never contact the network.
|
|
508
|
+
They cover grid mapping, statistics, resampling, indices, area summaries and
|
|
509
|
+
polygon masks, chunk-aligned reads, the raw chunk cache, the requests-backed
|
|
510
|
+
store, client fetching, and export archives.
|
|
511
|
+
|
|
512
|
+
## Licence
|
|
513
|
+
|
|
514
|
+
The **source code** of `ro-climate` is released under the [MIT License](https://github.com/strainu/ro_climate/blob/main/LICENSE).
|
|
515
|
+
|
|
516
|
+
The **ANM data** accessed through the library are published under
|
|
517
|
+
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The software
|
|
518
|
+
licence does not replace or relax the data licence. When you redistribute
|
|
519
|
+
data obtained through this library, keep the attribution and licence notice
|
|
520
|
+
in the exported metadata and README files.
|
|
521
|
+
|
|
522
|
+
## Citation
|
|
523
|
+
|
|
524
|
+
If you use the data in a publication, cite the data provider and describe the
|
|
525
|
+
dataset, for example:
|
|
526
|
+
|
|
527
|
+
> Administrația Națională de Meteorologie (ANM), *ANM daily gridded
|
|
528
|
+
> meteorological cube*, CC BY 4.0, via data.gov.ro; cube assembly and hosting
|
|
529
|
+
> by West University of Timișoara (ROCS / EOCube.RO).
|
|
530
|
+
|
|
531
|
+
If you use the library itself, you may also link to
|
|
532
|
+
<https://github.com/strainu/ro_climate>.
|
|
533
|
+
|
|
534
|
+
## Contributing
|
|
535
|
+
|
|
536
|
+
Bug reports and pull requests are welcome at
|
|
537
|
+
<https://github.com/strainu/ro_climate/issues>. Run `python -m pytest -q`
|
|
538
|
+
before opening a pull request. Tests must remain offline: use synthetic data
|
|
539
|
+
and fake HTTP sessions rather than live downloads.
|