Skip to content

Commit 606850e

Browse files
committed
use hourly wind speeds again and allow global download
1 parent f9ecafa commit 606850e

2 files changed

Lines changed: 133 additions & 36 deletions

File tree

atlite/datasets/era5.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from dask.utils import SerializableLock
2727
from numpy import atleast_1d
2828

29-
from atlite.gis import maybe_swap_spatial_dims
29+
from atlite.gis import maybe_swap_spatial_dims, rotate
3030
from atlite.pv.solar_position import SolarPosition
3131
from atlite.wind import calculate_windspeed_bias_correction
3232

@@ -525,28 +525,38 @@ def retrieve_data(
525525

526526

527527
def retrieve_windspeed_average(
528-
cutout,
529-
height,
530-
first_year=1980,
531-
last_year=None,
532-
data_format="grib",
528+
cutout=None,
529+
height: int = 100,
530+
first_year: int = 2008,
531+
last_year: int | None = 2017,
532+
data_format: Literal["grib", "netcdf"] = "grib",
533533
**retrieval_params,
534534
):
535535
"""
536536
Retrieve average windspeed from `first_year` to `last_year`
537537
538+
The default time-period 2008-2017 was chosen to align with the simulation
539+
period of GWA3.
540+
538541
Parameters
539542
----------
540-
cutout : atlite.Cutout
541-
Cutout for which to retrieve windspeeds from CDS
543+
cutout : atlite.Cutout or None
544+
Cutout for which to retrieve windspeeds from CDS. If no cutout is
545+
specified the global means are retrieved at native resolution 0.25/0.25.
542546
height : int
543-
Height of windspeeds (ERA5 typically knows about 10m, 100m, 150m?)
544-
first_year : int
547+
Height of windspeeds (ERA5 typically knows about 10m, 100m)
548+
first_year : int, defaults to 2008
545549
First year to take into account
546-
last_year : int, optional
550+
last_year : int, defaults to 2017
547551
Last year to take into account (if omitted takes the previous year)
552+
data_format : {"grib", "netcdf"}
553+
Data format to use for retrieving from CDS.
548554
**retrieval_params
549555
556+
References
557+
----------
558+
https://globalwindatlas.info/
559+
550560
Returns
551561
-------
552562
DataArray
@@ -555,32 +565,56 @@ def retrieve_windspeed_average(
555565
if last_year is None:
556566
last_year = datetime.date.today().year - 1
557567

558-
ds = retrieve_data(
559-
"reanalysis-era5-single-levels-monthly-means",
560-
chunks=cutout.chunks,
561-
product_type="monthly_averaged_reanalysis",
562-
variable=[
563-
f"{height}m_u_component_of_wind",
564-
f"{height}m_v_component_of_wind",
565-
],
566-
area=_area(cutout.coords),
567-
grid=[cutout.dx, cutout.dy],
568-
year=[str(y) for y in range(first_year, last_year + 1)],
569-
month=[f"{m:02}" for m in range(1, 12 + 1)],
570-
time=["00:00"],
571-
data_format=data_format,
572-
**retrieval_params,
568+
retrieval_params = (
569+
{
570+
"dataset": "reanalysis-era5-single-levels",
571+
"product_type": "reanalysis",
572+
}
573+
| (
574+
{
575+
"area": _area(cutout.coords),
576+
"chunks": cutout.chunks,
577+
"grid": f"{cutout.dx}/{cutout.dy}",
578+
}
579+
if cutout is not None
580+
else {}
581+
)
582+
| retrieval_params
573583
)
574-
ds = _rename_and_clean_coords(ds)
575584

576-
return (
577-
sqrt(ds[f"u{height}"] ** 2 + ds[f"v{height}"] ** 2)
578-
.mean("time")
579-
.assign_attrs(
580-
units=ds[f"u{height}"].attrs["units"],
581-
long_name=f"{height} metre wind speed as long run average",
585+
def retrieve_chunk(year):
586+
ds = retrieve_data(
587+
variable=[
588+
f"{height}m_u_component_of_wind",
589+
f"{height}m_v_component_of_wind",
590+
],
591+
year=[year],
592+
month=[f"{m:02d}" for m in range(1, 12 + 1)],
593+
day=[f"{d:02d}" for d in range(1, 31 + 1)],
594+
time=[f"{h:02d}" for h in range(0, 23 + 1)],
595+
data_format=data_format,
596+
**retrieval_params,
582597
)
583-
)
598+
ds = _rename_and_clean_coords(ds)
599+
600+
if cutout is None:
601+
# the default longitude range of CDS is [0, 360], while [-180, 180] is standard
602+
ds = rotate(ds)
603+
604+
return (
605+
sqrt(ds[f"u{height}"] ** 2 + ds[f"v{height}"] ** 2)
606+
.mean("time")
607+
.assign_attrs(
608+
units=ds[f"u{height}"].attrs["units"],
609+
long_name=f"{height} metre wind speed as long run average",
610+
)
611+
)
612+
613+
years = range(first_year, last_year + 1)
614+
return xr.concat(
615+
compute(*(delayed(retrieve_chunk)(str(year)) for year in years)),
616+
dim=pd.Index(years, name="year"),
617+
).mean("year")
584618

585619

586620
def get_data_windspeed_bias_correction(cutout, retrieval_params, creation_parameters):
@@ -681,7 +715,7 @@ def retrieve_once(time):
681715
elif feature == "windspeed_bias_correction":
682716
return func(
683717
cutout,
684-
retrieval_params=dict(tmpdir=tmpdir, lock=lock, data_format=data_format),
718+
retrieval_params=retrieval_params,
685719
creation_parameters=creation_parameters,
686720
)
687721

atlite/gis.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import logging
99
import multiprocessing as mp
1010
from collections import OrderedDict
11+
from functools import singledispatch
1112
from pathlib import Path
1213
from warnings import catch_warnings, simplefilter
1314

@@ -771,7 +772,69 @@ def maybe_swap_spatial_dims(ds, namex="x", namey="y"):
771772
return ds.isel(**swaps) if swaps else ds
772773

773774

774-
def _as_transform(x, y):
775+
@singledispatch
776+
def rotate(x):
777+
"""
778+
Rotate the x coordinate of a dataarray from 0 to 360, to standard
779+
coordinates between -180 and 180 degrees.
780+
"""
781+
_, *types = rotate.registry.keys()
782+
raise ValueError(f"rotate is only implemented for: {', '.join(types)}")
783+
784+
785+
@rotate.register(xr.Dataset)
786+
@rotate.register(xr.DataArray)
787+
def _(da):
788+
x = da.indexes["x"]
789+
790+
assert x.is_monotonic_increasing
791+
i = x.searchsorted(180.0)
792+
if i >= len(x):
793+
return da
794+
795+
da = xr.concat(
796+
[
797+
da.isel(x=slice(i, None)).assign_coords(x=x[i:] - 360.0),
798+
da.isel(x=slice(None, i)),
799+
],
800+
dim="x",
801+
)
802+
803+
if "lon" in da.coords:
804+
da = da.assign_coords(lon=da.coords["x"])
805+
806+
return da
807+
808+
809+
@rotate.register
810+
def _(x: pd.Index) -> pd.Index:
811+
assert x.is_monotonic_increasing
812+
i = x.searchsorted(180.0)
813+
if i >= len(x):
814+
return x
815+
816+
return (x[i:] - 360.0).append(x[:i])
817+
818+
819+
def _as_transform(x: pd.Index | np.ndarray, y: pd.Index | np.ndarray) -> rio.Affine:
820+
"""
821+
Derive a transform for equally-spaced longitude, latitude coordinates
822+
823+
x and y are assumed to point to the cell center, while the transform points
824+
to the lower, left corner.
825+
826+
Parameters
827+
----------
828+
x : Index
829+
x coordinates
830+
y : Index
831+
y coordinates
832+
833+
Returns
834+
-------
835+
transform : Affine
836+
Rasterio Affine object
837+
"""
775838
lx, rx = x[[0, -1]]
776839
ly, uy = y[[0, -1]]
777840

0 commit comments

Comments
 (0)