"""Read and write WAsP .lib files.
The .lib format stores generalized wind climate data as Weibull
parameters (A, k) and wind direction frequencies for a single point
across multiple generalized roughness classes and heights.
Data contract: wdfreq values are stored as percentages (0-100) in the
file but returned/expected as normalized fractions (0-1) by these
functions. Conversion happens automatically. A and k are raw Weibull
parameters (no conversion).
"""
import re
import numpy as np
from windkit.io.wasp._common import detect_encoding
[docs]
def read_lib(filepath, encoding="infer"):
"""Read a WAsP .lib file and return parsed data.
Parameters
----------
filepath : str or Path
Path to the ``.lib`` file.
encoding : str, optional
File encoding. If ``"infer"`` (default), the encoding is detected
automatically.
Returns
-------
dict
Dictionary with keys:
- ``"A"`` : ndarray, shape (n_sectors, n_heights, n_roughnesses)
- ``"k"`` : ndarray, same shape as *A*
- ``"wdfreq"`` : ndarray, shape (n_sectors, n_roughnesses),
normalized values (0-1)
- ``"gen_roughness"`` : ndarray, shape (n_roughnesses,)
- ``"gen_height"`` : ndarray, shape (n_heights,)
- ``"coords"`` : dict with ``"west_east"``, ``"south_north"``,
``"height"`` (values are *None* when the file has no
``<coordinates>`` tag)
- ``"metadata"`` : dict with ``"description"``
"""
if encoding == "infer":
encoding = detect_encoding(filepath)
with open(filepath, encoding=encoding) as f:
desc = f.readline().strip()
n_roughnesses, n_heights, n_sectors = (
int(x) for x in f.readline().strip().split()
)
gen_roughness = np.array(f.readline().strip().split(), dtype=np.float64)
gen_height = np.array(f.readline().strip().split(), dtype=np.float64)
wdfreq = np.zeros((n_sectors, n_roughnesses), dtype=np.float64)
A = np.zeros((n_sectors, n_heights, n_roughnesses), dtype=np.float64)
k = np.zeros((n_sectors, n_heights, n_roughnesses), dtype=np.float64)
def _read_floats(f):
return [float(x) for x in f.readline().strip().split()]
for i in range(n_roughnesses):
wdfreq[:, i] = _read_floats(f)
for j in range(n_heights):
A[:, j, i] = _read_floats(f)
k[:, j, i] = _read_floats(f)
coords = {"west_east": None, "south_north": None, "height": None}
match = re.search(r"<coordinates>(.*?)</coordinates>", desc)
if match:
we, sn, h = (float(x) for x in match.group(1).split(","))
coords = {"west_east": we, "south_north": sn, "height": h}
if gen_roughness[0] != 0.0:
raise ValueError(
f"The first generalized roughness must be 0.0, got {gen_roughness[0]}"
)
# Normalize wdfreq from percent (0-100) to fraction (0-1)
wdfreq_sum = wdfreq.sum(axis=0, keepdims=True)
wdfreq_sum[wdfreq_sum == 0] = 1.0 # avoid division by zero
wdfreq = wdfreq / wdfreq_sum
return {
"A": A,
"k": k,
"wdfreq": wdfreq,
"gen_roughness": gen_roughness,
"gen_height": gen_height,
"coords": coords,
"metadata": {"description": desc},
}
[docs]
def write_lib(filepath, A, k, wdfreq, gen_roughness, gen_height, coords, metadata=None):
"""Write Weibull parameters to a WAsP .lib file.
Parameters
----------
filepath : str or Path
Output file path.
A : array_like, shape (n_sectors, n_heights, n_roughnesses)
Weibull A parameters.
k : array_like, shape (n_sectors, n_heights, n_roughnesses)
Weibull k parameters.
wdfreq : array_like, shape (n_sectors, n_roughnesses)
Normalized wind direction frequencies (0-1).
gen_roughness : array_like, shape (n_roughnesses,)
Generalized roughness classes.
gen_height : array_like, shape (n_heights,)
Generalized heights.
coords : dict
Dictionary with ``"west_east"``, ``"south_north"``, ``"height"``.
metadata : dict, optional
Dictionary with ``"description"`` key.
"""
content = format_lib(A, k, wdfreq, gen_roughness, gen_height, coords, metadata)
with open(filepath, "w", newline="\r\n") as f:
f.write(content)