Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ title: "atlite: A Lightweight Python Package for Calculating Renewable Power Pot
journal: The Journal of Open Source Software
doi: "10.21105/joss.03294"
repository: "https://github.com/PyPSA/atlite"
version: "0.2.5"
date-released: "2021-06-24"
version: "0.7.0"
date-released: "2026-10-03"
license: "MIT"
authors:
- family-names: "Hofmann"
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ For major next development goals, consult our `vision and roadmap project <https
Installation
============

To install you need a working installation running Python 3.10 or above
To install you need a working installation running Python 3.11 or above
and we strongly recommend using either miniconda or anaconda for package
management.

Expand Down
81 changes: 49 additions & 32 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,69 @@ Upcoming Release
To use the features already you have to install the ``master`` branch, e.g.
``pip install git+https://github.com/pypsa/atlite``.

**Features**
`v0.7.0 <https://github.com/PyPSA/atlite/releases/tag/v0.7.0>`__ (3rd October 2026)
=======================================================================================

* Add ``buffer_geometry`` argument to ``ExclusionContainer.add_raster``. Choose
between the historic ``"diamond"`` buffer (default) and a new ``"circular"``
buffer that computes a geometrically accurate Euclidean buffer, correct in
diagonal directions.
**Breaking**

* Drop support for Python 3.10. atlite now requires Python 3.11 or above
(https://github.com/PyPSA/atlite/pull/507).
* Add the required dependencies ``zarr>=3``, ``obstore>=0.9`` and ``eccodeslib``
(not on Windows) (https://github.com/PyPSA/atlite/pull/507).
* Rewrite CSP conversion to model the collector geometry. ``convert_csp`` now uses
``SurfaceOrientation`` with tracking (horizontal for parabolic trough, dual-axis for
solar tower) instead of the former ``atlite.csp`` module, which was removed. This
changes the CSP output. The ``working-with-csp`` example was updated accordingly
(https://github.com/PyPSA/atlite/pull/504).

**Features**

* Add an Earth Data Hub (EDH) source for ERA5 as a new dataset module ``era5-edh``,
which serves data without the CDS processing queue. Configure a DestinE API key
(see :doc:`installation`) and pass ``module="era5-edh"`` when creating a cutout.
Only the native 0.25°×0.25° grid is supported; use ``module="era5"`` (CDS) for
other resolutions.
* Implement glofas dataset which contains daily river discharge. ``cutout.hydro()`` now
returns discharge if ``cutout.module`` contains ``"glofas"``.
(https://github.com/PyPSA/atlite/pull/498)
* Rewrite CSP conversion to model the collector geometry. ``convert_csp`` now uses
``SurfaceOrientation`` with tracking (horizontal for parabolic trough, dual-axis for
solar tower) instead of the former ``atlite.csp`` module, which was removed. The
``working-with-csp`` example was updated accordingly.
(https://github.com/PyPSA/atlite/pull/504)
other resolutions (https://github.com/PyPSA/atlite/pull/507).
* Add the GloFAS dataset with daily river discharge. ``cutout.hydro()`` now returns
discharge if ``cutout.module`` contains ``"glofas"``
(https://github.com/PyPSA/atlite/pull/498).
* Add ``buffer_geometry`` argument to ``ExclusionContainer.add_raster``. Choose
between the historic ``"diamond"`` buffer (default) and a new ``"circular"``
buffer that computes a geometrically accurate Euclidean buffer, correct in
diagonal directions (https://github.com/PyPSA/atlite/pull/513).
* ``SurfaceOrientation`` now also accepts a plain orientation dict
(``{"slope": ..., "azimuth": ...}``) in addition to a callable.
(https://github.com/PyPSA/atlite/pull/504)
* Meshgrid ``regrid``/reproject now allows dask to rechunk the spatial input, so that
multi-chunk spatial planes are merged before reprojection.
(https://github.com/PyPSA/atlite/pull/504)
(``{"slope": ..., "azimuth": ...}``) in addition to a callable
(https://github.com/PyPSA/atlite/pull/504).
* Add type annotations and NumPy-style docstrings across the code base, checked
with ``mypy`` and stricter ``ruff`` rules. String options now use ``Literal``
types (https://github.com/PyPSA/atlite/pull/492).

**Bug fixes**

* Fix the ``units`` and ``long_name`` attributes of the ERA5 variables ``height``
(was geopotential ``m**2 s**-2``), ``wnd_azimuth`` (was the ``u100`` wind
component) and ``solar_altitude``/``solar_azimuth`` (inherited ``latitude``).
Only metadata changes, values are unchanged (`#509 <https://github.com/PyPSA/atlite/issues/509>`_).

* Fix ``get_oedb_windturbineconfig`` applying the documented ``turbine_type``
search parameter to the value of ``name``. Searching by ``turbine_type``
alone raised ``KeyError: 'name'``, and combining it with ``name`` silently
ignored the requested turbine type.

* Fix ``Cutout.line_rating`` passing line azimuth in radians while
``convert_line_rating`` interpreted ``psi`` as degrees. Azimuths are now
computed in degrees, matching the documented unit.

computed in degrees, matching the documented unit
(https://github.com/PyPSA/atlite/pull/492).
* Fix raster exclusions being wrongly applied with rasterio 1.5.1, which
perturbs source values colliding with the reprojection nodata value. Rasters
added with ``nodata=0`` excluded their whole extent.
added with ``nodata=0`` excluded their whole extent
(https://github.com/PyPSA/atlite/pull/518).
* Fix ``regrid`` failing on inputs with multiple spatial chunks (e.g. SARAH).
Dask now merges the spatial chunks before reprojection
(https://github.com/PyPSA/atlite/pull/504).
* Fix ``get_oedb_windturbineconfig`` applying the documented ``turbine_type``
search parameter to the value of ``name``. Searching by ``turbine_type``
alone raised ``KeyError: 'name'``, and combining it with ``name`` silently
ignored the requested turbine type (https://github.com/PyPSA/atlite/pull/519).
* Fix the ``units`` and ``long_name`` attributes of the ERA5 variables ``height``
(was geopotential ``m**2 s**-2``), ``wnd_azimuth`` (was the ``u100`` wind
component) and ``solar_altitude``/``solar_azimuth`` (inherited ``latitude``).
Only metadata changes, values are unchanged
(https://github.com/PyPSA/atlite/pull/521).
* Fix ``aggregate_matrix`` (used by ``convert_and_aggregate`` with ``matrix``,
``shapes`` or ``layout``) raising a ``TypeError`` with xarray 2026.9.0. For
data not backed by dask, a ``pandas.MultiIndex`` passed as ``index`` to
``convert_and_aggregate`` is now kept; before, it was replaced by its level names
(https://github.com/PyPSA/atlite/pull/524).

`v0.6.1 <https://github.com/PyPSA/atlite/releases/tag/v0.6.1>`__ (21st April 2026)
=======================================================================================
Expand Down
5 changes: 4 additions & 1 deletion atlite/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ def aggregate_matrix(
).assign_coords(coords)
return cast("xr.DataArray", result)
da = da.stack(spatial=("y", "x")).transpose("spatial", "time")
return xr.DataArray(matrix * da, [index, da.coords["time"]])
result = xr.DataArray(
matrix * da, dims=[*coords.dims, "time"], coords={"time": da.coords["time"]}
)
return result.assign_coords(coords)
4 changes: 1 addition & 3 deletions atlite/datasets/era5_edh.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,7 @@ def get_data_height(cutout: Cutout) -> xr.Dataset:
Dataset containing ``height``.
"""
ds = _load_feature(cutout, "height", static=True)
ds = _add_height(ds)
ds["height"].attrs["units"] = "m**2 s**-2"
return ds
return _add_height(ds)


_HANDLERS: dict[str, Handler] = {
Expand Down
52 changes: 52 additions & 0 deletions test/test_aggregate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: Contributors to atlite <https://github.com/pypsa/atlite>
#
# SPDX-License-Identifier: MIT

"""Tests for spatial aggregation with a sparse matrix."""

import numpy as np
import pandas as pd
import pytest
import scipy.sparse as sp
import xarray as xr

from atlite.aggregate import aggregate_matrix
from atlite.utils import ensure_coords

BUSES = pd.Index(["a", "b", "c"], name="bus")
REGIONS = pd.MultiIndex.from_tuples(
[("DE", 1), ("DE", 2), ("FR", 1)], names=["country", "n"]
)


@pytest.fixture
def da():
rng = np.random.default_rng(42)
return xr.DataArray(
rng.random((5, 3, 4)),
dims=["time", "y", "x"],
coords={
"time": pd.date_range("2020-01-01", periods=5, freq="h"),
"y": [50.0, 51.0, 52.0],
"x": [5.0, 6.0, 7.0, 8.0],
},
)


@pytest.mark.parametrize("chunked", [False, True], ids=["numpy", "dask"])
@pytest.mark.parametrize(
"index",
[BUSES, REGIONS, ensure_coords(REGIONS)],
ids=["index", "multiindex", "coords"],
)
def test_aggregate_matrix_keeps_index(da, index, chunked):
matrix = sp.csr_matrix(np.random.default_rng(0).random((3, 12)))
data = da.chunk({"time": 2}) if chunked else da

result = aggregate_matrix(data, matrix, index).transpose(..., "time")

expected = matrix @ da.stack(spatial=("y", "x")).transpose("spatial", "time").values
np.testing.assert_allclose(result.values, expected)
xr.testing.assert_identical(result.coords["time"], da.coords["time"])
dim = result.dims[0]
assert result.indexes[dim].equals(index if isinstance(index, pd.Index) else REGIONS)
Loading