Source code for pycequeau.meteo.calculators.wind_speed
from __future__ import annotations
import numpy as np
import xarray as xr
from .base import MeteoCalculator
[docs]
class WindSpeedCalculator(MeteoCalculator):
"""Compose daily wind speed from daily mean u10 and v10 components."""
variable_name = "wind_speed"
default_output_name = "wind"
source_variable_groups = (
("u10", "10m_u_component_of_wind"),
("v10", "10m_v_component_of_wind"),
)
@classmethod
def _build_output_dataset(
cls,
source_dataarrays: dict[str, xr.DataArray],
*,
output_name: str,
**kwargs,
) -> xr.Dataset:
"""Build a wind-speed dataset from two daily wind-component inputs."""
source_names = list(source_dataarrays)
u10 = source_dataarrays[source_names[0]]
v10 = source_dataarrays[source_names[1]]
cls._validate_compatible_inputs(u10, v10)
wind_speed = cls.wind_speed_from_components_dataarray(
u10,
v10,
output_name=output_name,
)
result = xr.Dataset(
data_vars={output_name: wind_speed},
coords=u10.coords,
attrs={},
)
result.attrs["derivation_variable"] = cls.variable_name
result.attrs["derivation_source_variable"] = (
f"{u10.name or 'u10'}, {v10.name or 'v10'}"
)
result.attrs["derivation_method"] = "daily_vector_magnitude_from_daily_mean_components"
return result
[docs]
@classmethod
def wind_speed_from_components_dataarray(
cls,
u10: xr.DataArray,
v10: xr.DataArray,
*,
output_name: str = "wind",
) -> xr.DataArray:
"""Compose wind speed from two data-array wind components."""
speed = cls.wind_speed_from_components_array(u10, v10)
speed = speed.rename(output_name)
speed.attrs = dict(u10.attrs)
speed.attrs["long_name"] = "Daily 10 m wind speed"
speed.attrs["standard_name"] = "wind_speed"
speed.attrs["source_variables"] = f"{u10.name or 'u10'}, {v10.name or 'v10'}"
speed.attrs["source_time_step"] = "daily"
speed.attrs["aggregation"] = "daily_vector_magnitude_from_daily_mean_components"
speed.attrs["conversion_method"] = "wind speed composed from daily mean u10 and v10"
speed.attrs["conversion_equation"] = "wind = sqrt(u10^2 + v10^2)"
return speed
[docs]
@staticmethod
def wind_speed_from_components_array(
u10: np.ndarray | xr.DataArray,
v10: np.ndarray | xr.DataArray,
) -> np.ndarray | xr.DataArray:
"""Compose wind speed from array-like wind components."""
return np.sqrt(u10**2 + v10**2)
@staticmethod
def _validate_compatible_inputs(u10: xr.DataArray, v10: xr.DataArray) -> None:
"""Validate that the wind-component inputs are dimensionally compatible."""
if u10.dims != v10.dims:
raise ValueError(f"Wind component dimensions do not match: {u10.dims} vs {v10.dims}")
for dim in u10.dims:
if dim not in v10.coords:
raise ValueError(f"Coordinate '{dim}' is missing from the second wind component.")
if not np.array_equal(u10[dim].values, v10[dim].values):
raise ValueError(f"Coordinate '{dim}' does not match between wind components.")