Source code for windkit.metadata

# (c) 2022 DTU Wind Energy
"""
Variable Metadata for CF Conventions

This module contains a dictionary that lists all possible variables in the WindKit
system. These are then used to make metadata dictionaries for each object in their
respective modules.

In addition to the metadata, there are convience functions for updating object metadata.
"""
import inspect
import json
from datetime import datetime, timezone
from logging import Logger
from pathlib import Path

import xarray as xr

from ._version import version
from .config import CONFIG
from .data_structures import DataStructures
from .spatial._struct import get_spatial_struct

logger = Logger(__name__)

with open(Path(__file__).resolve().parent / "all_vars.json") as f:
    ALL_VARS_META = json.load(f)

_GLOBAL_CONVENTIONS = {
    "Conventions": "CF-1.8",
}


[docs] def create_coords(data, dimname, meta): """Create simple coordinate DataArray with metadata. Parameters ---------- data : list or numpy.ndarray Data to fill the datarray with. dimname : str Name of the dimension, also name of the key in metadata dict. meta : dict dictionary containing metadata for the coordinate. Returns ------- xarray.DataArray DataArray containing the coordinate. """ # Create data array da = xr.DataArray(data, dims=dimname) da = da.assign_coords({dimname: da}) da[dimname].attrs = {**meta[dimname]} return da
# wind time series object _TS_ATTRS = { "wind_speed": ALL_VARS_META["wind_speed"], "wind_direction": ALL_VARS_META["wind_direction"], "object_type": DataStructures.TS.value, } # wind histogram (count) object _HIS_ATTRS = { "wv_count": ALL_VARS_META["wv_count"], "count_bins_exceeded": ALL_VARS_META["count_bins_exceeded"], "object_type": DataStructures.HIS.value, } # stability time series object _TS_STAB_ATTRS = { "temp_scale": ALL_VARS_META["temp_scale"], } # stability histogram object _HIS_STAB_ATTRS = { "sum_temp_scale": ALL_VARS_META["sum_temp_scale"], "sum_squared_temp_scale": ALL_VARS_META["sum_squared_temp_scale"], "sum_pblh": ALL_VARS_META["sum_pblh"], "count_bins_exceeded": ALL_VARS_META["count_bins_exceeded"], "object_type": DataStructures.HIS_STAB.value, } # mean stability histogram _MEAN_STAB_ATTRS = { "mean_temp_scale_land": ALL_VARS_META["mean_temp_scale_land"], "rms_temp_scale_land": ALL_VARS_META["rms_temp_scale_land"], "mean_pblh_scale_land": ALL_VARS_META["mean_pblh_scale_land"], "mean_temp_scale_sea": ALL_VARS_META["mean_temp_scale_sea"], "rms_temp_scale_sea": ALL_VARS_META["rms_temp_scale_sea"], "mean_pblh_scale_sea": ALL_VARS_META["mean_pblh_scale_sea"], } # mean stability histogram _MEAN_BARO_ATTRS = { "mean_dgdz": ALL_VARS_META["mean_dgdz"], "mean_dgdz_dir": ALL_VARS_META["mean_dgdz_dir"], } # baroclinicity histogram object _HIS_BARO_ATTRS = { "wv_count": ALL_VARS_META["wv_count"], "sum_dugdz": ALL_VARS_META["sum_dugdz"], "sum_dvgdz": ALL_VARS_META["sum_dvgdz"], "count_bins_exceeded": ALL_VARS_META["count_bins_exceeded"], } _BWC_ATTRS = { "wdfreq": ALL_VARS_META["wdfreq"], "wsfreq": ALL_VARS_META["wsfreq"], "meridian_convergence": ALL_VARS_META["meridian_convergence"], "object_type": DataStructures.BWC.value, } _GEOWC_ATTRS = { "geo_wv_freq": ALL_VARS_META["geo_wv_freq"], "geo_turn": ALL_VARS_META["geo_turn"], "object_type": DataStructures.GEOWC.value, } _ELEV_ROSE_ATTRS = { "site_elev": ALL_VARS_META["elevation"], "rix": ALL_VARS_META["rix"], "dirrix": ALL_VARS_META["dirrix"], "flow_sep_height": ALL_VARS_META["flow_sep_height"], "nray": ALL_VARS_META["bz_nray"], "grid": ALL_VARS_META["bz_grid"], "c": ALL_VARS_META["bz_c"], "s": ALL_VARS_META["bz_s"], "w": ALL_VARS_META["bz_w"], "hr1": ALL_VARS_META["bz_hr1"], "hi1": ALL_VARS_META["bz_hi1"], "hi2": ALL_VARS_META["bz_hi2"], "r": ALL_VARS_META["bz_r"], "object_type": DataStructures.ELEV_ROSE.value, } _ELEV_ROSE_COORDS = { "radial_dist": ALL_VARS_META["radial_dist"], "radials_x_radial_dist": ALL_VARS_META["radials_x_radial_dist"], "radials": ALL_VARS_META["radials"], } _ROU_ROSE_ATTRS = { "z0": ALL_VARS_META["rou_rose_roughness"], "slf": ALL_VARS_META["rou_rose_slf"], "dist": ALL_VARS_META["rou_rose_dist"], "displ": ALL_VARS_META["displacement_height"], "nrch": ALL_VARS_META["rou_rose_nrch"], "object_type": DataStructures.ROU_ROSE.value, } _ROU_ROSE_COORDS = { "max_rou_changes1": ALL_VARS_META["max_rou_changes1"], "max_rou_changes": ALL_VARS_META["max_rou_changes"], } _GEWC_ATTRS = { "year": ALL_VARS_META["year"], "max_wspd": ALL_VARS_META["max_wspd"], "max_wdir": ALL_VARS_META["max_wdir"], "max_time": ALL_VARS_META["max_time"], "object_type": DataStructures.GEWC.value, } _LINCOM_V50_ATTRS = { "max_wspd": ALL_VARS_META["max_wspd"], "max_wdir": ALL_VARS_META["max_wdir"], "ierror": ALL_VARS_META["lincom_lut_err"], "object_type": DataStructures.LINCOM_V50.value, } _LINCOM_V50_LUT_ATTRS = { "WS": ALL_VARS_META["wind_speed"], "WD": ALL_VARS_META["wind_direction"], "object_type": DataStructures.LINCOM_V50_LUT.value, } _LINCOM_WIND_LEVEL_ATTRS = { "WS": ALL_VARS_META["wind_speed"], "U": ALL_VARS_META["U"], "V": ALL_VARS_META["V"], "W": ALL_VARS_META["W"], "flow_inclination": ALL_VARS_META["flow_inclination"], "Z0": ALL_VARS_META["dyn_roughness"], "terrain_inclination": ALL_VARS_META["terrain_inclination"], "DU_DX": ALL_VARS_META["DU_DX"], "DV_DX": ALL_VARS_META["DV_DX"], "DW_DX": ALL_VARS_META["DW_DX"], "DU_DY": ALL_VARS_META["DU_DY"], "DV_DY": ALL_VARS_META["DV_DY"], "DW_DY": ALL_VARS_META["DW_DY"], "DU_DZ": ALL_VARS_META["DU_DZ"], "DV_DZ": ALL_VARS_META["DV_DZ"], "DW_DZ": ALL_VARS_META["DW_DZ"], "object_type": DataStructures.LINCOM_WIND_LEVEL.value, } _LINCOM_WIND_POINT_ATTRS = { "elevation": ALL_VARS_META["elevation"], "height": ALL_VARS_META["height"], "WS": ALL_VARS_META["wind_speed"], "WD": ALL_VARS_META["wind_direction"], "flow_inclination": ALL_VARS_META["flow_inclination"], "USTAR": ALL_VARS_META["USTAR"], "DU_DX": ALL_VARS_META["DU_DX"], "DV_DX": ALL_VARS_META["DV_DX"], "DU_DY": ALL_VARS_META["DU_DY"], "DV_DY": ALL_VARS_META["DV_DY"], "DU_DZ": ALL_VARS_META["DU_DZ"], "DV_DZ": ALL_VARS_META["DV_DZ"], "DTilt_DX": ALL_VARS_META["DTilt_DX"], "DTilt_DY": ALL_VARS_META["DTilt_DY"], "DTilt_DZ": ALL_VARS_META["DTilt_DZ"], "ALPHA": ALL_VARS_META["shear_exp"], "object_type": DataStructures.LINCOM_WIND_POINT.value, } _V50_GUMBEL_ATTRS = { "gumbel_alpha": ALL_VARS_META["gumbel_alpha"], "gumbel_beta": ALL_VARS_META["gumbel_beta"], "extreme_wspd": ALL_VARS_META["extreme_wspd"], "extreme_uncert": ALL_VARS_META["extreme_uncert"], "ierror": ALL_VARS_META["gumbel_fit_err"], "object_type": DataStructures.V50_GUMBEL.value, } # This is used by rastermap and holds all potential maptypes _MAP_TYPE_ATTRS = { "elevation": ALL_VARS_META["elevation"], "roughness": ALL_VARS_META["roughness"], "landcover": ALL_VARS_META["landcover"], "speedup": ALL_VARS_META["cfd_speedups"], "turning": ALL_VARS_META["cfd_turnings"], "flow_inclination": ALL_VARS_META["cfd_flow_inclination"], "turbulence_intensity": ALL_VARS_META["cfd_turbulence_intensity"], "displacement_height": ALL_VARS_META["displacement_height"], "landmask": ALL_VARS_META["landmask"], "fetch": ALL_VARS_META["fetch"], "object_type": DataStructures.MAP_TYPE.value, } # this is the data structure after adding extra information to # a basic weibull wind climate _MET_ATTRS = { "A_combined": ALL_VARS_META["weib_A_combined"], "k_combined": ALL_VARS_META["weib_k_combined"], "wspd_sector": ALL_VARS_META["wind_speed_sector"], "wspd": ALL_VARS_META["wind_speed"], "wspd_combined": ALL_VARS_META["wind_speed_combined"], "air_density": ALL_VARS_META["air_density"], "power_density_sector": ALL_VARS_META["power_density_sector"], "power_density": ALL_VARS_META["power_density"], "power_density_combined": ALL_VARS_META["power_density_combined"], "object_type": DataStructures.MET.value, } _SECTOR_COORD_ATTRS = { "sector": ALL_VARS_META["sector"], "sector_ceil": ALL_VARS_META["sector_ceil"], "sector_floor": ALL_VARS_META["sector_floor"], "wind_direction": ALL_VARS_META["wind_direction"], } _WSBIN_COORD_ATTRS = { "wsbin": ALL_VARS_META["wsbin"], "wsceil": ALL_VARS_META["wsceil"], "wsfloor": ALL_VARS_META["wsfloor"], "wind_speed": ALL_VARS_META["wind_speed"], } _SPECTRUM_ATTRS = { "spectrum_freq": ALL_VARS_META["spectrum_freq"], "spectrum_power": ALL_VARS_META["spectrum_power"], "spec_corr_fac": ALL_VARS_META["spec_corr_fac"], "object_type": DataStructures.SPECTRUM.value, } _TOPO_EFFECTS_ATTRS = { "z0meso": ALL_VARS_META["z0meso"], "slfmeso": ALL_VARS_META["slfmeso"], "displ": ALL_VARS_META["displacement_height"], "flow_sep_height": ALL_VARS_META["flow_sep_height"], "user_def_speedups": ALL_VARS_META["user_def_speedups"], "orographic_speedups": ALL_VARS_META["orographic_speedups"], "obstacle_speedups": ALL_VARS_META["obstacle_speedups"], "roughness_speedups": ALL_VARS_META["roughness_speedups"], "user_def_turnings": ALL_VARS_META["user_def_turnings"], "orographic_turnings": ALL_VARS_META["orographic_turnings"], "obstacle_turnings": ALL_VARS_META["obstacle_turnings"], "roughness_turnings": ALL_VARS_META["roughness_turnings"], "dirrix": ALL_VARS_META["dirrix"], "site_elev": ALL_VARS_META["elevation"], "rix": ALL_VARS_META["rix"], "object_type": DataStructures.TOPO_EFFECTS.value, } _TOPO_CFD_EFFECTS_ATTRS = { "z0meso": ALL_VARS_META["z0meso"], "cfd_speedups": ALL_VARS_META["cfd_speedups"], "cfd_turnings": ALL_VARS_META["cfd_turnings"], "cfd_turbulence_intensity": ALL_VARS_META["cfd_turbulence_intensity"], "cfd_flow_inclination": ALL_VARS_META["cfd_flow_inclination"], "site_elev": ALL_VARS_META["elevation"], "object_type": DataStructures.TOPO_CFD_EFFECTS.value, } # this is the basic data structure returned by # a WAsP downscaling, in WAsP GUI often referred # to as the pwc (predicted wind climate) _WEIB_ATTRS = { "A": ALL_VARS_META["weib_A"], "k": ALL_VARS_META["weib_k"], "wdfreq": ALL_VARS_META["wdfreq"], "object_type": DataStructures.WEIB.value, } _WTG_ATTRS = { "wind_speed": ALL_VARS_META["wind_speed"], "mode": ALL_VARS_META["wtg_mode"], "power_output": ALL_VARS_META["power_output"], "thrust_coefficient": ALL_VARS_META["thrust_coefficient"], "air_density": ALL_VARS_META["air_density"], "stationary_thrust_coefficient": ALL_VARS_META["stationary_thrust_coefficient"], "wind_speed_cutin": ALL_VARS_META["wind_speed_cutin"], "wind_speed_cutout": ALL_VARS_META["wind_speed_cutout"], "rated_power": ALL_VARS_META["rated_power"], "name": ALL_VARS_META["wtg_model"], "rotor_diameter": ALL_VARS_META["rotor_diameter"], "hub_height": ALL_VARS_META["hub_height"], "object_type": DataStructures.WTG.value, } # Data structure returned by aep calculations _AEP_ATTRS = { "gross_AEP": ALL_VARS_META["gross_aep"], "gross_AEP_sector": ALL_VARS_META["gross_aep_sector"], "potential_AEP": ALL_VARS_META["potential_aep"], "potential_AEP_sector": ALL_VARS_META["potential_aep_sector"], "object_type": DataStructures.AEP.value, } # Data structure for a wind farm flow map _WF_FLOW_MAP_ATTRS = { "potential_AEP_sector": ALL_VARS_META["potential_aep_sector"], "gross_AEP_sector": ALL_VARS_META["gross_aep_sector"], "AEP_deficit_sector": ALL_VARS_META["aep_deficit_sector"], "wspd_sector": ALL_VARS_META["wind_speed"], # Is this right? "wspd_eff_sector": ALL_VARS_META["wind_speed_effective"], # Is this right? "wspd_deficit_sector": ALL_VARS_META["wind_speed_deficit"], # Is this right? "turbulence_intensity_eff_sector": ALL_VARS_META[ "turbulence_intensity_effective_sector" ], # there is a similar "wdfreq": ALL_VARS_META["wdfreq"], "potential_AEP": ALL_VARS_META["potential_aep"], "gross_AEP": ALL_VARS_META["potential_aep"], "AEP_deficit": ALL_VARS_META["aep_deficit"], "wspd": ALL_VARS_META["wind_speed"], # Is this right? "wspd_eff": ALL_VARS_META["wind_speed_effective"], # Is this right? "wspd_deficit": ALL_VARS_META["wind_speed_deficit"], # Is this right? "turbulence_intensity_eff": ALL_VARS_META[ "turbulence_intensity_effective" ], # there is a siilar "object_type": DataStructures.WF_FLOW_MAP.value, } def _update_local_attrs(da, var_dict): """Updates data varaible attributes Parameters ---------- da: xarray.DataArray WindKit DataArray to be updated vars_dict : dict Dictionary of attributes for the data variable Returns ------- xarray.DataArray The same DataArray with updated attributes """ # Update attributes if they are in the list otherwise inform the user try: da.attrs = {**da.attrs, **var_dict[da.name]} except KeyError as e: logger.info(f"KeyError{e}") if get_spatial_struct(da) is not None: da.attrs["grid_mapping"] = "crs" return da def _setup_user_global_attrs(): """ Defines the metadata asked to the user """ CONFIG.add_str("user_data", "name") CONFIG.add_email("user_data", "email") CONFIG.add_str("user_data", "institution")
[docs] def update_var_attrs(obj, var_dict): """Update all data variable attributes. Parameters ---------- obj : xarray.Dataset or xarray.DataArray WindKit Dataset of DataArray to be updated. vars_dict : dict Dictionary maping variable names to the attributes that should be used. Returns ------- xarray.Dataset or xarray.DataArray The same Dataset or DataArray with updated attributes. """ _setup_user_global_attrs() CONFIG.create_config() var_dict_copy = var_dict.copy() object_type = var_dict_copy.pop("object_type", None) if isinstance(obj, xr.Dataset): for var in obj.data_vars: obj[var] = _update_local_attrs(obj[var], var_dict) obj.attrs["Conventions"] = "CF-1.8" obj.attrs["Package name"] = __name__.split(".")[0] obj.attrs["Package version"] = version obj.attrs["Creation date"] = ( datetime.now(timezone.utc).replace(microsecond=0).isoformat() ) if object_type is not None: obj.attrs["Object type"] = object_type obj.attrs["author"] = CONFIG.get_option("user_data", "name") obj.attrs["author_email"] = CONFIG.get_option("user_data", "email") obj.attrs["institution"] = CONFIG.get_option("user_data", "institution") elif isinstance(obj, xr.DataArray): obj = _update_local_attrs(obj, var_dict) else: raise ValueError( "Can only add attributes to xarray.Dataset or xarray.DataArray objects." ) return obj
[docs] def update_history(ds): """Update history global attribute. Updates the global history attribute for a xarray Dataset. Parameters ---------- ds : xarray.Dataset WindKit Dataset to be updated. Returns ------- xarray.Dataset The same Dataset with updated attribute. """ current_utc = datetime.now(timezone.utc).replace(microsecond=0).isoformat() package = __name__.split(".")[0] # Get the function call that was used before calling update_history. # NOTE: If call is from the python interpreter, we can't determine the context, so # include a generic message. function_context = inspect.stack()[2].code_context if function_context is None: function_call = "Unknown python interpreter command." else: function_call = function_context[0][:-1] if "=" in function_call: function_call = function_call[function_call.index("=") + 1 :] if " " in function_call: function_call = function_call[function_call.index(" ") + 1 :] history_to_add = ( current_utc + ":" + "\t" + package + "==" + version + "\t" + function_call ) if "history" in ds.attrs.keys(): ds.attrs["history"] = ds.attrs["history"] + "\n" + history_to_add else: ds.attrs["history"] = history_to_add return ds