windkit 2.0.1__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. windkit-2.1.0/.triage-policies.yml +47 -0
  2. {windkit-2.0.1 → windkit-2.1.0}/CHANGELOG.md +102 -0
  3. {windkit-2.0.1 → windkit-2.1.0}/PKG-INFO +20 -20
  4. {windkit-2.0.1 → windkit-2.1.0}/pyproject.toml +25 -20
  5. {windkit-2.0.1 → windkit-2.1.0}/windkit/__init__.py +6 -2
  6. {windkit-2.0.1 → windkit-2.1.0}/windkit/_errors.py +1 -1
  7. windkit-2.1.0/windkit/_version.py +24 -0
  8. {windkit-2.0.1 → windkit-2.1.0}/windkit/cfd.py +5 -3
  9. {windkit-2.0.1 → windkit-2.1.0}/windkit/integrations/_earth_engine.py +1 -1
  10. {windkit-2.0.1 → windkit-2.1.0}/windkit/integrations/_gwa_map_api.py +46 -4
  11. {windkit-2.0.1 → windkit-2.1.0}/windkit/integrations/_planetary_computer.py +5 -5
  12. windkit-2.1.0/windkit/io/__init__.py +12 -0
  13. windkit-2.1.0/windkit/io/wasp/__init__.py +14 -0
  14. windkit-2.1.0/windkit/io/wasp/_common.py +31 -0
  15. windkit-2.1.0/windkit/io/wasp/lib.py +198 -0
  16. windkit-2.1.0/windkit/io/wasp/owc.py +90 -0
  17. windkit-2.1.0/windkit/io/wasp/tab.py +149 -0
  18. windkit-2.1.0/windkit/io/windio/__init__.py +86 -0
  19. windkit-2.1.0/windkit/io/windio/readers.py +410 -0
  20. windkit-2.1.0/windkit/io/windio/translate/__init__.py +49 -0
  21. windkit-2.1.0/windkit/io/windio/translate/bwc.py +515 -0
  22. windkit-2.1.0/windkit/io/windio/translate/common.py +1298 -0
  23. windkit-2.1.0/windkit/io/windio/translate/tswc.py +339 -0
  24. windkit-2.1.0/windkit/io/windio/translate/wind_turbines.py +338 -0
  25. windkit-2.1.0/windkit/io/windio/translate/wtg.py +399 -0
  26. windkit-2.1.0/windkit/io/windio/translate/wwc.py +411 -0
  27. windkit-2.1.0/windkit/io/windio/utils.py +385 -0
  28. windkit-2.1.0/windkit/io/windio/writers.py +360 -0
  29. {windkit-2.0.1 → windkit-2.1.0}/windkit/ltc/__init__.py +1 -1
  30. {windkit-2.0.1 → windkit-2.1.0}/windkit/ltc/_validation.py +2 -2
  31. {windkit-2.0.1 → windkit-2.1.0}/windkit/ltc/mcp.py +122 -54
  32. {windkit-2.0.1 → windkit-2.1.0}/windkit/ltc/regression.py +39 -24
  33. {windkit-2.0.1 → windkit-2.1.0}/windkit/ltc/scores.py +6 -11
  34. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/__init__.py +2 -1
  35. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/_colormaps.py +1 -1
  36. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/_helpers.py +1 -1
  37. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/histogram.py +246 -35
  38. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/operational_curves.py +1 -1
  39. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/raster_plot.py +1 -1
  40. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/roughness_rose.py +1 -1
  41. windkit-2.1.0/windkit/plot/spatial_plots.py +264 -0
  42. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/time_series.py +1 -1
  43. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/vertical_profile.py +1 -1
  44. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/wind_rose.py +1 -1
  45. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/__init__.py +1 -1
  46. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_bbox.py +21 -4
  47. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_crs.py +23 -5
  48. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_dimensions.py +1 -1
  49. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_interpolation.py +144 -15
  50. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_point.py +194 -86
  51. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_raster.py +16 -2
  52. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_struct.py +111 -1
  53. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_utm.py +1 -1
  54. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_vector.py +1 -1
  55. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_vertical.py +1 -1
  56. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/spatial.py +90 -29
  57. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/_vectormap_gml.py +1 -1
  58. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/_vectormap_helpers.py +1 -1
  59. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/get_map.py +17 -6
  60. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/landcover.py +1 -1
  61. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversion/lines_to_poly.py +6 -1
  62. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversion/poly_to_lines.py +8 -1
  63. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/raster_map.py +122 -19
  64. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/topography.py +1 -1
  65. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/vector_map.py +60 -12
  66. {windkit-2.0.1 → windkit-2.1.0}/windkit/tutorial_data.py +45 -8
  67. {windkit-2.0.1 → windkit-2.1.0}/windkit/utils.py +1 -1
  68. {windkit-2.0.1 → windkit-2.1.0}/windkit/weibull.py +16 -5
  69. {windkit-2.0.1 → windkit-2.1.0}/windkit/weng_workspace.py +1 -1
  70. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind.py +27 -19
  71. windkit-2.1.0/windkit/wind_climate/_synthetic_time_series.py +279 -0
  72. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/binned_wind_climate.py +178 -182
  73. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/generalized_wind_climate.py +112 -241
  74. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/geostrophic_wind_climate.py +5 -35
  75. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/predicted_wind_climate.py +3 -1
  76. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/time_series_wind_climate.py +485 -38
  77. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/weibull_wind_climate.py +106 -13
  78. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/wind_climate.py +117 -141
  79. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_data.py +1 -1
  80. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/loss.py +1 -1
  81. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/uncertainty.py +1 -1
  82. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/wind_turbine_generator.py +87 -32
  83. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/wind_turbines.py +36 -3
  84. {windkit-2.0.1 → windkit-2.1.0}/windkit/workspace.py +1 -1
  85. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/_validate.py +1 -1
  86. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/data_structures.py +1 -1
  87. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/direction.py +1 -1
  88. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/empty.py +8 -8
  89. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/metadata.py +1 -1
  90. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/sector.py +1 -1
  91. {windkit-2.0.1 → windkit-2.1.0}/windkit/xarray_structures/wsbin.py +1 -1
  92. {windkit-2.0.1 → windkit-2.1.0}/windkit.egg-info/SOURCES.txt +20 -0
  93. windkit-2.0.1/windkit/_version.py +0 -34
  94. {windkit-2.0.1 → windkit-2.1.0}/LICENSE +0 -0
  95. {windkit-2.0.1 → windkit-2.1.0}/README.md +0 -0
  96. {windkit-2.0.1 → windkit-2.1.0}/setup.cfg +0 -0
  97. {windkit-2.0.1 → windkit-2.1.0}/windkit/_rvea_xml.py +0 -0
  98. {windkit-2.0.1 → windkit-2.1.0}/windkit/_weibull_nb.py +0 -0
  99. {windkit-2.0.1 → windkit-2.1.0}/windkit/config.py +0 -0
  100. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/all_vars.json +0 -0
  101. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/color_table_roughness.json +0 -0
  102. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/CGLS-LC100.json +0 -0
  103. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/CORINE.json +0 -0
  104. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/ESA_CCI.json +0 -0
  105. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/GWA4_micro.json +0 -0
  106. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/Globcover.json +0 -0
  107. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/MODIS.json +0 -0
  108. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/NEWA_micro_v2.json +0 -0
  109. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/landcover_tables/WorldCover.json +0 -0
  110. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/loss_tables/dtu_loss_table.csv +0 -0
  111. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/map_file_header_to_epsg.json +0 -0
  112. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/map_file_header_to_epsg_read.json +0 -0
  113. {windkit-2.0.1 → windkit-2.1.0}/windkit/data/uncertainty_tables/dtu_uncertainty_table.csv +0 -0
  114. {windkit-2.0.1 → windkit-2.1.0}/windkit/import_manager.py +0 -0
  115. {windkit-2.0.1 → windkit-2.1.0}/windkit/integrations/__init__.py +0 -0
  116. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/color.py +0 -0
  117. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/elevation_map.py +0 -0
  118. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/landcover_map.py +0 -0
  119. {windkit-2.0.1 → windkit-2.1.0}/windkit/plot/wind_turbine.py +0 -0
  120. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/README.md +0 -0
  121. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_conversions.py +0 -0
  122. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_cuboid.py +0 -0
  123. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_latitude.py +0 -0
  124. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/_metpy_natural_neighbors.py +0 -0
  125. {windkit-2.0.1 → windkit-2.1.0}/windkit/spatial/decorators.py +0 -0
  126. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/__init__.py +0 -0
  127. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversion/__init__.py +0 -0
  128. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversion/_polygons_constraints.py +0 -0
  129. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversion/helper_functions.py +0 -0
  130. {windkit-2.0.1 → windkit-2.1.0}/windkit/topography/map_conversions.py +0 -0
  131. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_climate/__init__.py +0 -0
  132. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/__init__.py +0 -0
  133. {windkit-2.0.1 → windkit-2.1.0}/windkit/wind_farm/power_curve.py +0 -0
@@ -0,0 +1,47 @@
1
+ resource_rules:
2
+ issues:
3
+ rules:
4
+ # Label issues as Stale after 6 months of inactivity
5
+ - name: Label stale issues
6
+ conditions:
7
+ state: opened
8
+ date:
9
+ attribute: updated_at
10
+ condition: older_than
11
+ interval_type: months
12
+ interval: 6
13
+ forbidden_labels:
14
+ - Stale
15
+ - "Status: Blocked"
16
+ - Meta-issue
17
+ actions:
18
+ labels:
19
+ - Stale
20
+ comment: |
21
+ This issue has been inactive for 6 months and has been labeled `~Stale`.
22
+
23
+ If this issue is still relevant, please comment or update it to reset
24
+ the inactivity timer. Otherwise, it will be automatically closed in
25
+ 3 months.
26
+
27
+ # Close issues 3 months after the Stale label was added
28
+ - name: Close stale issues
29
+ conditions:
30
+ state: opened
31
+ date:
32
+ attribute: updated_at
33
+ condition: older_than
34
+ interval_type: months
35
+ interval: 3
36
+ labels:
37
+ - Stale
38
+ forbidden_labels:
39
+ - "Status: Blocked"
40
+ - Meta-issue
41
+ actions:
42
+ status: close
43
+ comment: |
44
+ This issue has been inactive and is being closed
45
+ automatically per our [stale issue policy](CONTRIBUTING.md#stale-issue-policy).
46
+
47
+ If this issue is still relevant, feel free to reopen it.
@@ -2,6 +2,108 @@
2
2
 
3
3
  All major changes are listed here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## [2.1.0 (2026-04-22)](https://docs.wasp.dk/windkit/v2.1.0/)
8
+
9
+ ### Highlights
10
+
11
+ * **LinRegMCP with residual noise** — new `LinRegMCP.predict_with_noise()` samples Gaussian residuals per sector to recover realistic wind-speed distributions that preserve variance. `MCPRegressor.predict(return_std=True)` exposes the per-timestep residual standard deviation, and `LinearRegression.residual_std` is computed and stored during `fit()`.
12
+ * **New `windkit.io.wasp` low-level subpackage** — read and write `.tab`, `.lib`, and `.owc` / `.omwc` files as plain dicts with numpy arrays, without xarray overhead. `read_tab` / `write_tab` / `format_tab`, `read_lib` / `write_lib` / `format_lib`, `read_owc` / `parse_owc_element`.
13
+ * **Correlated synthetic TSWC data** — `create_tswc(not_empty=True)` now generates temporally and spatially correlated wind data using an AR(1) / Weibull model (one year at 10-minute frequency by default). New `create_tswc_pair` creates correlated TSWC pairs with configurable speed bias, directional bias, and target correlation.
14
+ * **windIO format support (experimental)** — new `windkit.io.windio` subpackage adds readers and writers for the IEA [windIO format](https://ieawindsystems.github.io/windIO/), covering BWC / WWC / TSWC wind climates, wind turbines / WTGs, and full plant composition. Top-level readers and writers (`read_wwc`, `read_bwc`, `read_tswc`, `read_wind_turbines`, `read_wtg`, and their `*_to_file` counterparts) now accept YAML. See *New features* below for the full API.
15
+ * **Dependency minimum bumps (breaking)** — minimum supported versions raised: numpy 2.0, xarray 2024.9, pandas 2.3, scipy 1.13, geopandas 1.0, rasterio 1.4, pyproj 3.7, lxml 5.2, netcdf4 1.7. Users on older versions must upgrade.
16
+
17
+ ### New features
18
+
19
+ * New `windkit.plot.spatial_structure_2d` and `windkit.plot.spatial_structure_3d` functions for visualising the geometry of WindKit spatial objects (`point`, `stacked_point`, `raster`, `cuboid`). Both functions accept an optional `ax` argument and support subsampling for large datasets. The 3D function also draws vertical tower lines for multi-height structures (`stacked_point`, `cuboid`), which can be toggled via `show_tower_lines`.
20
+ * `create_tswc` now generates temporally and spatially correlated synthetic wind data using an AR(1)/Weibull model when `not_empty=True`, replacing the previous uncorrelated random fill. The default date range is now one year at 10-min frequency (previously ten years).
21
+ * New `create_tswc_pair` function for generating correlated TSWC pairs with configurable speed bias, directional bias, and target correlation.
22
+ * New low-level `windkit.io.wasp` subpackage for reading and writing WAsP file formats without xarray overhead. Initial support for `.tab` files via `read_tab`, `write_tab`, and `format_tab`. These return plain dicts with numpy arrays and use normalized (0-1) frequency values.
23
+ * Added `.lib` file support to `windkit.io.wasp` via `read_lib`, `write_lib`, and `format_lib`. These return plain dicts with numpy arrays and use normalized (0-1) direction frequency values.
24
+ * Added `.owc`/`.omwc` file support to `windkit.io.wasp` via `read_owc` and `parse_owc_element`. These parse WAsP observed wind climate XML files and return plain dicts with numpy arrays.
25
+ * New `style="combined"` option for `histogram()` that shows the marginal (all-sector average) wind speed distribution as a stepped line, per-sector conditional distributions as faint stepped lines, an optional emergent Weibull curve, and a metrics annotation box with mean wind speed, power density, and Weibull parameters.
26
+ * Added `vrt` to the raster map supported file formats.
27
+ * `create_geowc` now accepts a `wsbin_width` argument to control the wind-speed bin width on the returned geostrophic wind climate.
28
+ * New `LinRegMCP.predict_with_noise()` method for predicting with Gaussian noise sampled from the per-sector residual standard deviation, recovering realistic wind speed distributions that preserve variance.
29
+ * `MCPRegressor.predict()` now accepts `return_std=True` to optionally return per-timestep residual standard deviations alongside predictions.
30
+ * `LinearRegression.residual_std` is now computed and stored during `fit()`, providing the per-sector residual standard deviation.
31
+
32
+ #### windIO format support (experimental)
33
+
34
+ Added support for reading and writing wind data in the [windIO format](https://ieawindsystems.github.io/windIO/), an open YAML-based data exchange standard developed by the IEA Wind Systems Engineering community. Only the Plant schema is supported.
35
+
36
+ * New module `windkit.io.windio` with:
37
+ * **Readers**: `read_plant`, `read_site`, `read_energy_resource`, `read_wind_resource`, `read_wind_farm`, `read_layout`, `read_turbine`
38
+ * **Writers**: `write_plant`, `write_turbine` (with optional `modular=True` for split files)
39
+ * `export_site` — convert windkit wind climate datasets (WWC/BWC/TSWC) to windIO site format and write to disk (netCDF or YAML)
40
+ * **Translation functions**:
41
+ * `wwc_from_windio` / `wwc_to_windio` — Weibull wind climate
42
+ * `bwc_from_windio` / `bwc_to_windio` — Binned wind climate
43
+ * `tswc_from_windio` / `tswc_to_windio` — Time series wind climate
44
+ * `wind_turbines_from_windio` / `wind_turbines_to_windio` — turbine layouts
45
+ * `wtg_from_windio` / `wtg_to_windio` — WTG power/thrust curves
46
+ * `compose_plant` — combine wind climate, turbines, and WTGs into a complete plant structure
47
+ * `resolve_includes` — expand `!include` directives in windIO files
48
+ * Top-level readers (`read_wwc`, `read_bwc`, `read_tswc`, `read_wind_turbines`, `read_wtg`) now accept YAML files.
49
+ * Top-level writers (`wwc_to_file`, `bwc_to_file`, `tswc_to_file`) now support YAML export.
50
+ * New function `tswc_to_file` for writing time series wind climates.
51
+ * All reader/writer functions support a `level` parameter for partial file operations (e.g., `level="wind_resource"` to read/write only the wind resource portion).
52
+ * Multi-height wind climate support.
53
+ * IEA reference turbine compatibility (tested with IEA-15MW, IEA-22MW).
54
+ * All `*_from_windio` translation functions now automatically detect and resolve `!include` markers, so callers can pass unresolved windIO dicts directly without calling `resolve_includes` first.
55
+
56
+ ### Bug fixes
57
+
58
+ * `interp_structured_like`, `interp_unstructured_like`, `fit_weibull_wasp_m1_m3_fgtm`, `fit_weibull_wasp_m1_m3`, and `fit_weibull_k_sumlogm` now preserve the dtype of the input data. Previously, all these functions unconditionally upcast results to float64.
59
+ * Fixed `interp_unstructured_like` returning `NaN` near the dateline in geographic CRSs by wrapping longitudes around the target region before building the unstructured interpolator. Longitude wrapping only activates when CRS metadata is present.
60
+ * Fixed `clip_with_margin` to handle reversed coordinate bounds (e.g., ERA5/CFSR data with south_north going from 90 to -90).
61
+ * Fixed typo "marging" to "margin" in `clip_with_margin` warning message.
62
+ * Fixed `BBox.from_ds` and `BBox.reproject_to_utm` raising `ValueError` for single-point datasets where `minx == maxx` or `miny == maxy`. Zero-size bounding boxes are now allowed (by relaxing validation in `BBox.from_cornerpts`).
63
+ * Fixed crash in `nearest_points` (and other spatial operations) when called on a dataset that was previously subset from a stacked-point structure. Internal MultiIndex metadata left from the original stacking would be out of sync with the reduced dataset size, causing an error during spatial conversion.
64
+ * Reprojecting is now loss-less for raster datasets, due to original coordinates being kept internally.
65
+ * Fixed raster map saving with NumPy>=2.0 by passing numpy arrays to rasterio.
66
+ * Fixed `set_crs` crash for CRS types that cannot be converted to WKT1_GDAL (e.g. rotated lat-lon) — now emits a warning instead.
67
+ * Fixed `ValueError` in LTC MCP caused by incorrect unpacking of `wd_to_sector` return values, which had made all MCP functionality (`LinRegMCP`, `VarRatMCP`) non-functional.
68
+ * Fixed mutable default argument (`model_kws={}`) in `MCPRegressor.__init__()`.
69
+ * Fixed `LinearRegression.fit()` not returning `self`.
70
+ * Fixed `wasserstein_distance()` not extracting `.values.flatten()` like other score functions.
71
+ * Fixed `np.empty_like` in `MCPRegressor.predict()` leaving uninitialized values (now uses NaN).
72
+ * Fixed overly broad `except Exception` in `validate_dataset_ltc()` to catch only `WindkitValidationError`.
73
+ * Fixed incorrect docstrings claiming `quantiles` defaults to `True` in `LinRegMCP` / `VarRatMCP`.
74
+ * Fixed sector wrap-around bug in `MCPBase._wd_to_sector()` where wind directions near 0°/360° were assigned to invalid sector index -1.
75
+ * Fixed bug in `tswc_resample` which used vector averages for wind speed. Now (correctly) uses average magnitudes instead.
76
+ * Fixed `create_tswc_pair` where the second dataset silently ignored the `speed_tau` argument.
77
+
78
+ ### Breaking changes
79
+
80
+ * Minimum supported dependency versions raised: numpy 2.0, xarray 2024.9, pandas 2.3, scipy 1.13, geopandas 1.0, rasterio 1.4, pyproj 3.7, lxml 5.2, netcdf4 1.7. Users on older versions must upgrade.
81
+
82
+ ### Deprecations
83
+
84
+ * Writing multi-point datasets to multiple `.tab` files via `bwc_to_file` is deprecated and will be removed in windkit 3.0. Write single-point datasets instead.
85
+ * Writing multi-point datasets to multiple `.lib` files via `gwc_to_file` is deprecated and will be removed in windkit 3.0. Write single-point datasets instead.
86
+
87
+ ### Performance
88
+
89
+ * Preserve MultiIndex when converting between point and stacked_point/raster structures, avoiding unnecessary re-stacking and leading to small performance improvements for large datasets in many windkit and pywasp functions.
90
+
91
+ ### Changes
92
+
93
+ * Added explicit `compat`, `join`, and `data_vars` parameters to all `xr.merge` and `xr.concat` calls to suppress xarray FutureWarnings.
94
+ * Improved `_read_ts_windpro_txt` to parse date format from the windpro file header instead of hard-coding.
95
+ * Improved `create_raster_map` to generate synthetic 2D raster maps for elevation, roughness and landcover.
96
+ * Improved vector map reading in `_read_vector_map`.
97
+ * Added a `layer` argument and automatic layer detection for vector map files (`.gpkg`, `.zip`) when `layer=None`.
98
+ * Improved download retry logic for `get_tutorial_data`.
99
+ * Changed `read_wtg` to read multiple suggested heights as a new variable `suggested_heights` if present in WTG files.
100
+ * Wind climate dispatch (`mean_wind_speed`, `mean_power_density`, `mean_ws_moment`, `ws_cdf`, `ws_freq_gt_mean`) now uses capability-based detection instead of strict type-identity checks. Datasets that have the right fields but don't pass every validator check (e.g. missing `sector_ceil` / `sector_floor`) now work correctly.
101
+ * Generalized wind climate (GWC) datasets now dispatch to the Weibull handler for `mean_wind_speed`, `mean_power_density`, and `mean_ws_moment`, since they contain Weibull parameters. Previously these operations raised `NotImplementedError`.
102
+ * The `bysector` keyword is now forwarded consistently to all wind climate types, including geostrophic wind climate, where it was previously silently ignored.
103
+ * Standardized deprecation warnings to include the `since` and `removal` version in every message (e.g. "deprecated since windkit 2.1, removed in windkit 3.0").
104
+ * Give informative error in `get_vector_map` when the requested area is too large and when using `source="dtu"`.
105
+
106
+
5
107
  ## [2.0.1 (2026-01-29)](https://docs.wasp.dk/windkit/v2.0.1/)
6
108
 
7
109
  ### Highlights
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windkit
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: WindKit provides core functionalities for working with wind resource data
5
5
  Author: DTU Wind Energy
6
6
  License-Expression: BSD-3-Clause
@@ -11,36 +11,36 @@ Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: geopandas>=1.0
13
13
  Requires-Dist: h5netcdf
14
- Requires-Dist: lxml>=5.0
15
- Requires-Dist: netcdf4>=1.6
16
- Requires-Dist: numpy>=1.26
17
- Requires-Dist: packaging>=23
18
- Requires-Dist: pandas>=2.1
14
+ Requires-Dist: lxml>=5.2
15
+ Requires-Dist: netcdf4>=1.7
16
+ Requires-Dist: numpy>=2.0
17
+ Requires-Dist: packaging>=24.1
18
+ Requires-Dist: pandas>=2.3
19
19
  Requires-Dist: platformdirs>=4.3
20
20
  Requires-Dist: pydantic>=2.7
21
21
  Requires-Dist: pydantic-settings>=2.3
22
- Requires-Dist: pyproj>=3.6
22
+ Requires-Dist: pyproj>=3.7
23
23
  Requires-Dist: pyyaml
24
- Requires-Dist: rasterio>=1.3
24
+ Requires-Dist: rasterio>=1.4
25
25
  Requires-Dist: requests
26
26
  Requires-Dist: rioxarray>=0.17
27
- Requires-Dist: scipy>=1.12
27
+ Requires-Dist: scipy>=1.13
28
28
  Requires-Dist: shapely>=2.0
29
29
  Requires-Dist: toml
30
- Requires-Dist: xarray>=2023.9
30
+ Requires-Dist: xarray>=2024.9
31
31
  Provides-Extra: plots
32
- Requires-Dist: matplotlib>=3.8; extra == "plots"
33
- Requires-Dist: plotly>=5.12.0; extra == "plots"
34
- Requires-Dist: kaleido; extra == "plots"
32
+ Requires-Dist: matplotlib>=3.9; extra == "plots"
33
+ Requires-Dist: plotly>=5.21; extra == "plots"
34
+ Requires-Dist: kaleido>=1.0; extra == "plots"
35
35
  Provides-Extra: performance
36
- Requires-Dist: numba>=0.57.0; extra == "performance"
36
+ Requires-Dist: numba>=0.60; extra == "performance"
37
37
  Provides-Extra: data-apis
38
- Requires-Dist: adlfs; extra == "data-apis"
39
- Requires-Dist: earthengine-api; extra == "data-apis"
40
- Requires-Dist: fsspec; extra == "data-apis"
41
- Requires-Dist: planetary-computer; extra == "data-apis"
42
- Requires-Dist: pystac-client; extra == "data-apis"
43
- Requires-Dist: zarr>2.15; extra == "data-apis"
38
+ Requires-Dist: adlfs>=2024.4; extra == "data-apis"
39
+ Requires-Dist: earthengine-api>=1.0; extra == "data-apis"
40
+ Requires-Dist: fsspec>=2024.5; extra == "data-apis"
41
+ Requires-Dist: planetary-computer>=1.0; extra == "data-apis"
42
+ Requires-Dist: pystac-client>=0.8; extra == "data-apis"
43
+ Requires-Dist: zarr>=3.0; extra == "data-apis"
44
44
  Provides-Extra: dev
45
45
  Requires-Dist: dash; extra == "dev"
46
46
  Requires-Dist: ipython; extra == "dev"
@@ -19,41 +19,41 @@ requires-python = ">=3.11"
19
19
  dependencies = [
20
20
  "geopandas>=1.0",
21
21
  "h5netcdf",
22
- "lxml>=5.0",
23
- "netcdf4>=1.6",
24
- "numpy>=1.26",
25
- "packaging>=23",
26
- "pandas>=2.1",
22
+ "lxml>=5.2",
23
+ "netcdf4>=1.7",
24
+ "numpy>=2.0",
25
+ "packaging>=24.1",
26
+ "pandas>=2.3",
27
27
  "platformdirs>=4.3",
28
28
  "pydantic>=2.7",
29
29
  "pydantic-settings>=2.3",
30
- "pyproj>=3.6",
30
+ "pyproj>=3.7",
31
31
  "pyyaml",
32
- "rasterio>=1.3",
32
+ "rasterio>=1.4",
33
33
  "requests",
34
34
  "rioxarray>=0.17",
35
- "scipy>=1.12",
35
+ "scipy>=1.13",
36
36
  "shapely>=2.0",
37
37
  "toml",
38
- "xarray>=2023.9",
38
+ "xarray>=2024.9",
39
39
  ]
40
40
 
41
41
  [project.optional-dependencies]
42
42
  plots = [
43
- "matplotlib>=3.8",
44
- "plotly>=5.12.0",
45
- "kaleido",
43
+ "matplotlib>=3.9",
44
+ "plotly>=5.21",
45
+ "kaleido>=1.0",
46
46
  ]
47
47
  performance = [
48
- "numba>=0.57.0",
48
+ "numba>=0.60",
49
49
  ]
50
50
  data_apis = [
51
- "adlfs",
52
- "earthengine-api",
53
- "fsspec",
54
- "planetary-computer",
55
- "pystac-client",
56
- "zarr>2.15",
51
+ "adlfs>=2024.4",
52
+ "earthengine-api>=1.0",
53
+ "fsspec>=2024.5",
54
+ "planetary-computer>=1.0",
55
+ "pystac-client>=0.8",
56
+ "zarr>=3.0",
57
57
  ]
58
58
  dev = [
59
59
  "dash",
@@ -99,7 +99,6 @@ namespaces = true
99
99
  [tool.setuptools.package-data]
100
100
  "windkit.data" = ["*.json"]
101
101
  "windkit.data.landcover_tables" = ["*.json"]
102
- "windkit.data.plot" = ["*.json"]
103
102
  "windkit.data.loss_tables" = ["*.csv"]
104
103
  "windkit.data.uncertainty_tables" = ["*.csv"]
105
104
 
@@ -111,3 +110,9 @@ markers = [
111
110
  "slow: marks tests as slow (deselect with '-m \"not slow\"')",
112
111
  "performance: marks tests as performance tests (run with '-m performance')",
113
112
  ]
113
+
114
+ # Added warning filter for warnings that show up for min dependencies.
115
+ # These do not produce any errors and once packages are upgraded, these are not emitted
116
+ filterwarnings = [
117
+ "ignore:The 'shapely.geos' module is deprecated:DeprecationWarning:geopandas._compat",
118
+ ]
@@ -1,4 +1,4 @@
1
- # (c) 2022 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  WindKit provides an API for working with wind resource assessment related file formats.
4
4
  """
@@ -29,7 +29,9 @@ __all__ = [
29
29
  "is_tswc",
30
30
  "create_tswc",
31
31
  "read_tswc",
32
+ "tswc_to_file",
32
33
  "tswc_from_dataframe",
34
+ "create_tswc_pair",
33
35
  # Binned wind climate
34
36
  "validate_bwc",
35
37
  "is_bwc",
@@ -120,7 +122,7 @@ __all__ = [
120
122
  "polygons_to_lines",
121
123
  "snap_to_layer",
122
124
  "check_dead_ends",
123
- "check_lines_cross"
125
+ "check_lines_cross",
124
126
  # get_map
125
127
  "get_raster_map",
126
128
  "get_vector_map",
@@ -171,7 +173,9 @@ from windkit.wind_climate.time_series_wind_climate import (
171
173
  is_tswc,
172
174
  create_tswc,
173
175
  read_tswc,
176
+ tswc_to_file,
174
177
  tswc_from_dataframe,
178
+ create_tswc_pair,
175
179
  )
176
180
  from windkit.wind_climate import time_series_wind_climate # noqa: F401
177
181
  from windkit.wind_climate.binned_wind_climate import (
@@ -1,4 +1,4 @@
1
- # (c) 2022 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  Package exceptions message writing
4
4
  """
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2.1.0'
22
+ __version_tuple__ = version_tuple = (2, 1, 0)
23
+
24
+ __commit_id__ = commit_id = 'g0a6bf992d'
@@ -1,4 +1,4 @@
1
- # (c) 2022 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  Processes WAsP CFD volume results for use in PyWAsP
4
4
  """
@@ -124,13 +124,15 @@ def read_cfdres(path, crs):
124
124
  array = array.expand_dims(["height"])
125
125
  sector_list.append(array)
126
126
  # combine all data arrays into a dataset
127
- ds_sector = xr.merge(sector_list)
127
+ ds_sector = xr.merge(sector_list, compat="no_conflicts", join="outer")
128
128
  # load mesoscale roughness length
129
129
  ds_sector.coords["sector"] = sec_dir
130
130
  ds_sector = ds_sector.expand_dims(["sector"])
131
131
  all_arrays.append(ds_sector)
132
132
 
133
- ds_cfd = xr.concat(all_arrays, dim="sector")
133
+ ds_cfd = xr.concat(
134
+ all_arrays, dim="sector", data_vars="all", compat="equals", join="outer"
135
+ )
134
136
  ds_cfd["z0meso"] = (("sector",), z0meso)
135
137
  # make sure all the meta data for sectors are there
136
138
  ds_cfd = ds_cfd.assign_coords(
@@ -1,4 +1,4 @@
1
- # (c) 2022 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  Module that downloads elevation and roughness maps
4
4
 
@@ -1,4 +1,4 @@
1
- # (c) 2022 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  Module that downloads elevation and roughness maps from the GWA map api
4
4
  https://api.globalwindatlas.info/qgis-data/v1/corine_2018?points_str={query_string}.
@@ -113,7 +113,8 @@ def _add_safety_buffer(bbox, safety_buffer=None):
113
113
  Returns
114
114
  -------
115
115
  bbox : wk.spatial.BBox
116
- The bounding box with the added safety buffer.
116
+ The bounding box with the added safety buffer. If the bbox is zero-size or one-axis-degenerate,
117
+ it is returned unchanged as buffering is not applicable.
117
118
  """
118
119
  # some extra margins to make sure we select an area that is somewhat
119
120
  # larger than the requested square, so that there is no artifacts at the border
@@ -127,6 +128,10 @@ def _add_safety_buffer(bbox, safety_buffer=None):
127
128
  west_east_width = np.abs(xll - xur)
128
129
  south_north_width = np.abs(yll - yur)
129
130
 
131
+ # For zero-size or degenerate bboxes, skip buffering as it's not applicable
132
+ if west_east_width == 0 or south_north_width == 0:
133
+ return bbox
134
+
130
135
  if safety_buffer > west_east_width or safety_buffer > south_north_width:
131
136
  raise ValueError(
132
137
  "You should not buffer with more than the size of the bounding box itself. Reduce your safety_buffer."
@@ -184,11 +189,15 @@ def _get_vector_map_from_dtu(bbox, dataset="CORINE", safety_buffer=None):
184
189
  # full URL to submit
185
190
  url = f"https://api.globalwindatlas.info/qgis-data/v1/corine_2018?points_str={query_string}"
186
191
  data = requests.get(url)
187
- feats = data.json()["features"]
192
+
193
+ _raise_for_status_with_detail(data)
194
+
195
+ resp_json = data.json()
196
+ feats = resp_json.get("features")
188
197
 
189
198
  if feats:
190
199
  # there is some items in feats so we convert the GeoJSON to a geopandas dataframe
191
- gdf = gpd.GeoDataFrame.from_features(data.json()["features"], crs="EPSG:4326")
200
+ gdf = gpd.GeoDataFrame.from_features(feats, crs="EPSG:4326")
192
201
  lct = LandCoverTable.get_table("CORINE")
193
202
  lct_gdf = gpd.GeoDataFrame.from_dict(lct, orient="index")
194
203
  lct_gdf = lct_gdf.rename_axis(index="id")
@@ -203,3 +212,36 @@ def _get_vector_map_from_dtu(bbox, dataset="CORINE", safety_buffer=None):
203
212
  )
204
213
 
205
214
  return comb
215
+
216
+
217
+ def _raise_for_status_with_detail(response):
218
+ """
219
+ Raise a ConnectionError with useful detail if the HTTP response
220
+ indicates a failure. Preserves the previous message format.
221
+
222
+ Parameters
223
+ ----------
224
+ response : requests.Response
225
+ The HTTP response object to validate. If the response indicates an
226
+ error (non-success status code), a ConnectionError will be raised.
227
+
228
+ Raises
229
+ ------
230
+ ConnectionError
231
+ Raised when the response has an HTTP error status. The error message
232
+ includes the HTTP status code and, if present, the 'detail' field
233
+ extracted from the JSON body of the response.
234
+ """
235
+ try:
236
+ response.raise_for_status()
237
+ except requests.exceptions.RequestException as e:
238
+ msg = f"GWA API request failed with status {response.status_code}"
239
+ try:
240
+ detail = response.json().get("detail")
241
+ if detail:
242
+ msg = f"{msg}: {detail}"
243
+ else:
244
+ msg = f"{msg}: {e}"
245
+ except Exception:
246
+ msg = f"{msg}: {e}"
247
+ raise ConnectionError(msg) from e
@@ -1,4 +1,4 @@
1
- # (c) 2023 DTU Wind Energy
1
+ # (c) 2026 DTU Wind Energy
2
2
  """
3
3
  Module for downloading topography maps from microsofts planetary computer.
4
4
 
@@ -438,10 +438,10 @@ def _get_era5_from_planetary_computer(datetime, bbox=None, translate_to_windkit=
438
438
  ws100 = ws100.expand_dims(dim={"height": [100.0]})
439
439
  wd100 = wd100.expand_dims(dim={"height": [100.0]})
440
440
 
441
- ws = xr.concat([ws10, ws100], dim="height")
442
- wd = xr.concat([wd10, wd100], dim="height")
441
+ ws = xr.concat([ws10, ws100], dim="height", compat="equals", join="outer")
442
+ wd = xr.concat([wd10, wd100], dim="height", compat="equals", join="outer")
443
443
 
444
- ds_wind = xr.merge([ws, wd])
444
+ ds_wind = xr.merge([ws, wd], compat="no_conflicts", join="outer")
445
445
  ds_wind = ds_wind.transpose("time", "height", "south_north", "west_east")
446
446
 
447
447
  ds_no_wind = ds.drop_vars(
@@ -453,6 +453,6 @@ def _get_era5_from_planetary_computer(datetime, bbox=None, translate_to_windkit=
453
453
  ]
454
454
  )
455
455
 
456
- ds = xr.merge([ds_no_wind, ds_wind])
456
+ ds = xr.merge([ds_no_wind, ds_wind], compat="no_conflicts", join="outer")
457
457
 
458
458
  return ds
@@ -0,0 +1,12 @@
1
+ # (c) 2026 DTU Wind Energy
2
+ """
3
+ I/O module for windkit.
4
+
5
+ This module provides functions for reading and writing wind data in various formats.
6
+ """
7
+
8
+ __all__ = [
9
+ "windio",
10
+ ]
11
+
12
+ from windkit.io import windio
@@ -0,0 +1,14 @@
1
+ from windkit.io.wasp.lib import format_lib, read_lib, write_lib
2
+ from windkit.io.wasp.owc import parse_owc_element, read_owc
3
+ from windkit.io.wasp.tab import format_tab, read_tab, write_tab
4
+
5
+ __all__ = [
6
+ "read_lib",
7
+ "write_lib",
8
+ "format_lib",
9
+ "read_owc",
10
+ "parse_owc_element",
11
+ "read_tab",
12
+ "write_tab",
13
+ "format_tab",
14
+ ]
@@ -0,0 +1,31 @@
1
+ """Common utilities for WAsP file I/O."""
2
+
3
+ _ENCODINGS = ("ascii", "utf_8", "utf_16", "cp1256")
4
+
5
+
6
+ def detect_encoding(filepath):
7
+ """Detect the encoding of a file by trying common encodings.
8
+
9
+ Parameters
10
+ ----------
11
+ filepath : str or Path
12
+ Path to the file to detect encoding for.
13
+
14
+ Returns
15
+ -------
16
+ str
17
+ The detected encoding.
18
+
19
+ Raises
20
+ ------
21
+ ValueError
22
+ If no supported encoding can decode the file.
23
+ """
24
+ for encoding in _ENCODINGS:
25
+ try:
26
+ with open(filepath, encoding=encoding) as f:
27
+ f.read()
28
+ return encoding
29
+ except (UnicodeDecodeError, UnicodeError):
30
+ pass
31
+ raise ValueError(f"Unknown encoding for {filepath}, please save as ASCII or UTF-8")