Metadata-Version: 2.4
Name: pywrb
Version: 0.2
Summary: Wind Resource binary reader and writer
Author-email: Thijs van Winden <thijs.vanwinden@whiffle.nl>
License-Expression: MIT
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: numpy
Provides-Extra: xarray
Requires-Dist: xarray; extra == "xarray"
Requires-Dist: rioxarray; extra == "xarray"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: xarray; extra == "dev"
Requires-Dist: rioxarray; extra == "dev"
Requires-Dist: fsspec; extra == "dev"
Dynamic: license-file

# pywrb

The pywrb library for reading and writing WRB files.

## Features

- Read and write WRB files
- xarray integration for reading and writing datasets
- Variable naming convention encodes present dimensions via `_at_direction` and `_at_wind_speed` suffixes
- Surface variables (`elevation`, `roughness_length`, `vegetation_height`, `ground_porosity`) are always 2D
- `wrbinfo` CLI for inspecting WRB file metadata
- Cloud storage support via fsspec (optional)

## Requirements

- Python 3.10+
- NumPy

Optional:
- xarray + rioxarray (`pip install pywrb[xarray]`)
- fsspec for cloud/remote file access (`pip install fsspec`)

## Installation

```bash
pip install pywrb            # core only
pip install pywrb[xarray]    # with xarray support
pip install pywrb[dev]       # with development dependencies
```

## Usage

### xarray interface

For most use cases the xarray interface is recommended. Variables are named after their block meaning, with `_at_direction` and `_at_wind_speed` suffixes added automatically when those dimensions are present in the blocks. Surface variables (`elevation`, `roughness_length`, `vegetation_height`, `ground_porosity`) are always 2D and never receive suffixes.

#### Reading and plotting

```python
import xarray as xr

ds = xr.open_dataset("wind_resource.wrb", engine="pywrb")

# 2D surface variable
ds["elevation"].plot.imshow()

# Select a height and plot mean wind speed across the site
ds["mean_wind_speed"].sel(heights=100).plot.imshow()

# Directional mean wind speed — one panel per direction
ds["mean_wind_speed_at_direction"].sel(heights=100).plot(col="directions", col_wrap=4)
```

#### Supported fields

Variable names map directly to `pywrb.WRB_BLOCK_MEANING`. The table below shows the base name; append `_at_direction` and/or `_at_wind_speed` when those dimensions are present.

| xarray variable | `WRB_BLOCK_MEANING` | always 2D |
|---|---|:---:|
| `elevation` | `ELEVATION` | ✓ |
| `roughness_length` | `ROUGHNESS_LENGTH` | ✓ |
| `vegetation_height` | `VEGETATION_HEIGHT` | ✓ |
| `ground_porosity` | `GROUND_POROSITY` | ✓ |
| `mean_wind_speed` | `MEAN_WIND_SPEED` | |
| `weibull_scale` | `WEIBULL_SCALE` | |
| `weibull_shape` | `WEIBULL_SHAPE` | |
| `power` | `POWER` | |
| `turbulence_intensity` | `TURBULENCE_INTENSITY` | |
| `inflow_angle` | `INFLOW_ANGLE` | |
| `probability` | `PROBABILITY` | |
| `air_density` | `AIR_DENSITY` | |
| `vertical_velocity` | `VERTICAL_VELOCITY` | |
| `wind_shear_exponent` | `WIND_SHEAR_EXPONENT` | |
| `eloss` | `ELOSS` | |
| `uncertainty` | `UNCERTAINTY` | |

#### Writing

To write a dataset to WRB, variable names must follow the naming convention above. The dataset must have a CRS set via `rioxarray`. The example below creates a typical wind resource dataset with omnidirectional and directional Weibull parameters, turbulence intensity, and surface variables:

```python
import xarray as xr
import numpy as np

n_x, n_y, n_directions, n_wind_speed, n_heights = 128, 128, 12, 10, 3

ds = xr.Dataset(
    data_vars=dict(
        weibull_scale=(
            ["x", "y", "heights"],
            np.random.randn(n_x, n_y, n_heights),
        ),
        weibull_shape=(
            ["x", "y", "heights"],
            np.random.randn(n_x, n_y, n_heights),
        ),
        weibull_scale_at_direction=(
            ["x", "y", "directions", "heights"],
            np.random.randn(n_x, n_y, n_directions, n_heights),
        ),
        weibull_shape_at_direction=(
            ["x", "y", "directions", "heights"],
            np.random.randn(n_x, n_y, n_directions, n_heights),
        ),
        turbulence_intensity_at_direction_at_wind_speed=(
            ["x", "y", "directions", "wind_speeds", "heights"],
            np.random.randn(n_x, n_y, n_directions, n_wind_speed, n_heights),
        ),
        elevation=(["x", "y"], np.ones((n_x, n_y))),
        roughness_length=(["x", "y"], np.ones((n_x, n_y))),
    ),
    coords=dict(
        x=(["x"], np.arange(n_x)),
        y=(["y"], np.arange(n_y)),
        directions=(["directions"], np.linspace(0, 360, n_directions, endpoint=False)),
        wind_speeds=(["wind_speeds"], np.linspace(6, 15, n_wind_speed)),
        heights=(["heights"], [100, 150, 200]),
    ),
)
ds = ds.rio.write_crs("epsg:4326")
ds.pywrb.to_wrb("output.wrb")
```

### Low-level interface

The low-level interface provides direct access to individual WRB blocks. This is useful for exotic data that the xarray interface does not cover.

Reading an existing WRB file:

```python
import pywrb

with pywrb.open("wind_resource.wrb", mode="r") as wrb:
    for block, data in wrb:
        if block["meaning"] == pywrb.WRB_BLOCK_MEANING.ELEVATION:
            print(data)
```

Writing a new WRB file:

```python
import numpy as np
import pywrb

data = np.ones((10, 10))

with pywrb.open(
    "output.wrb",
    mode="w",
    crs="EPSG:25832",
    minx=0, miny=0, maxx=900, maxy=900,
    resolutionx=100, resolutiony=100,
    heights=[100],
    directions=0,
    wind_speeds=[],
) as wrb:
    wrb.add_block(data=data, meaning=pywrb.WRB_BLOCK_MEANING.ELEVATION, unit=pywrb.WRB_UNIT.METER)
    wrb.add_block(data=data + 1, meaning=pywrb.WRB_BLOCK_MEANING.MEAN_WIND_SPEED, height=100)
    wrb.write()
```

A file-like object can also be passed directly, which is useful for integrating with other libraries:

```python
with open("wind_resource.wrb", "rb") as f:
    with pywrb.WRBFile(f) as wrb:
        print(wrb.shape)
```

### Cloud storage with fsspec

Remote files can be opened by passing a URI directly to `WRBFile` or `pywrb.open()`. Any URI scheme supported by fsspec works (`s3://`, `gs://`, `az://`, etc.):

```bash
pip install fsspec s3fs
```

```python
import pywrb
import xarray as xr

# Low-level
with pywrb.open("s3://my-bucket/wind_resource.wrb", mode="r") as wrb:
    print(wrb.shape)

# xarray
ds = xr.open_dataset("s3://my-bucket/wind_resource.wrb", engine="pywrb")
```

Alternatively, open the file yourself with fsspec and pass the file object in:

```python
import fsspec
import pywrb

with fsspec.open("s3://my-bucket/wind_resource.wrb", "rb") as f:
    with pywrb.WRBFile(f) as wrb:
        print(wrb.shape)
```

### CLI

`wrbinfo` prints metadata about a WRB file, similar to `gdalinfo`:

```bash
wrbinfo wind_resource.wrb
```

```
Driver: WRB v2
CRS:    epsg:32632
Size:   127 x 127 (width x height)
Origin: (439887.0, 4833898.0)
Pixel:  (200.0, 200.0)
Extent: (439887.0, 4833898.0) - (465087.0, 4859098.0)

Directions (16): [0.0, 22.5, 45.0, ...]
Heights    (1):  [80]
Speeds     (0):  []

Blocks (69):
  [  0] ELEVATION
  [  1] ROUGHNESS_LENGTH
  [  2] WEIBULL_SCALE                   height=80.0
  ...
```

JSON output is also supported:

```bash
wrbinfo wind_resource.wrb --format json
```

## Limitations

### Multi-regime files

The WRB format supports multi-regime datasets where blocks carry a probability < 1.0, indicating they belong to one of several wind regimes that together sum to 1.0. The low-level `WRBFile` interface and `wrbinfo` CLI can read these files, but the xarray interface does not support them and will raise a `NotImplementedError`. Single-regime files (probability = 1.0, the default) are fully supported.

## Contributing

Contributions to `pywrb` are welcome! If you'd like to contribute, please fork the repository and submit a pull request.

## License

`pywrb` is licensed under the [MIT License](https://opensource.org/licenses/MIT).
