windkit 2.1.0rc1__tar.gz → 2.2.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 (157) hide show
  1. {windkit-2.1.0rc1 → windkit-2.2.0}/.triage-policies.yml +41 -0
  2. {windkit-2.1.0rc1 → windkit-2.2.0}/CHANGELOG.md +134 -1
  3. {windkit-2.1.0rc1 → windkit-2.2.0}/PKG-INFO +11 -11
  4. {windkit-2.1.0rc1 → windkit-2.2.0}/README.md +7 -5
  5. {windkit-2.1.0rc1 → windkit-2.2.0}/pyproject.toml +28 -8
  6. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/__init__.py +50 -0
  7. windkit-2.2.0/windkit/_cli.py +95 -0
  8. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/_errors.py +37 -7
  9. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/_version.py +3 -3
  10. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/_weibull_nb.py +59 -96
  11. windkit-2.2.0/windkit/config.py +213 -0
  12. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/all_vars.json +137 -7
  13. windkit-2.2.0/windkit/import_manager.py +87 -0
  14. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/integrations/_earth_engine.py +46 -32
  15. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/integrations/_planetary_computer.py +0 -31
  16. windkit-2.2.0/windkit/io/__init__.py +36 -0
  17. windkit-2.2.0/windkit/io/wasp/__init__.py +32 -0
  18. windkit-2.2.0/windkit/io/wasp/_common.py +187 -0
  19. windkit-2.2.0/windkit/io/wasp/_rvea.py +207 -0
  20. windkit-2.2.0/windkit/io/wasp/gwc.py +507 -0
  21. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/wasp/lib.py +71 -13
  22. windkit-2.2.0/windkit/io/wasp/map.py +705 -0
  23. windkit-2.2.0/windkit/io/wasp/owc.py +165 -0
  24. windkit-2.2.0/windkit/io/wasp/rsf.py +490 -0
  25. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/wasp/tab.py +42 -10
  26. windkit-2.2.0/windkit/io/wasp/wtg.py +258 -0
  27. windkit-2.2.0/windkit/io/wasp/wwh.py +666 -0
  28. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/bwc.py +24 -17
  29. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/ltc/mcp.py +57 -46
  30. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/_colormaps.py +4 -2
  31. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/elevation_map.py +2 -1
  32. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/histogram.py +4 -3
  33. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/landcover_map.py +6 -4
  34. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/raster_plot.py +7 -7
  35. windkit-2.2.0/windkit/spatial/README.md +73 -0
  36. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/__init__.py +3 -0
  37. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_bbox.py +1 -1
  38. windkit-2.2.0/windkit/spatial/_dimensions.py +235 -0
  39. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_interpolation.py +294 -73
  40. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_point.py +49 -48
  41. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_raster.py +36 -25
  42. windkit-2.2.0/windkit/spatial/_schemas.py +63 -0
  43. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_struct.py +180 -84
  44. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/spatial.py +753 -103
  45. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/_vectormap_gml.py +1 -3
  46. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/_vectormap_helpers.py +0 -28
  47. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/get_map.py +1 -1
  48. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversion/poly_to_lines.py +1 -1
  49. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversions.py +243 -243
  50. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/raster_map.py +289 -9
  51. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/topography.py +219 -77
  52. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/vector_map.py +267 -238
  53. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/tutorial_data.py +9 -8
  54. windkit-2.2.0/windkit/utils.py +79 -0
  55. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/weibull.py +133 -47
  56. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind.py +171 -40
  57. windkit-2.2.0/windkit/wind_climate/_correlated_fields.py +292 -0
  58. windkit-2.2.0/windkit/wind_climate/_validation.py +89 -0
  59. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/binned_wind_climate.py +379 -136
  60. windkit-2.2.0/windkit/wind_climate/extreme_wind_climate.py +419 -0
  61. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/generalized_wind_climate.py +133 -116
  62. windkit-2.2.0/windkit/wind_climate/geostrophic_wind_climate.py +197 -0
  63. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/predicted_wind_climate.py +25 -14
  64. windkit-2.2.0/windkit/wind_climate/stability.py +288 -0
  65. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/time_series_wind_climate.py +337 -90
  66. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/weibull_wind_climate.py +245 -600
  67. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/wind_climate.py +282 -126
  68. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/loss.py +28 -16
  69. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/uncertainty.py +6 -3
  70. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/wind_turbine_generator.py +194 -221
  71. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/wind_turbines.py +17 -21
  72. windkit-2.2.0/windkit/workspace.py +1759 -0
  73. windkit-2.2.0/windkit/xarray_structures/_schema.py +565 -0
  74. windkit-2.2.0/windkit/xarray_structures/empty.py +236 -0
  75. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/xarray_structures/metadata.py +57 -9
  76. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit.egg-info/SOURCES.txt +13 -2
  77. windkit-2.1.0rc1/windkit/_rvea_xml.py +0 -258
  78. windkit-2.1.0rc1/windkit/config.py +0 -215
  79. windkit-2.1.0rc1/windkit/import_manager.py +0 -44
  80. windkit-2.1.0rc1/windkit/io/__init__.py +0 -12
  81. windkit-2.1.0rc1/windkit/io/wasp/__init__.py +0 -14
  82. windkit-2.1.0rc1/windkit/io/wasp/_common.py +0 -31
  83. windkit-2.1.0rc1/windkit/io/wasp/owc.py +0 -90
  84. windkit-2.1.0rc1/windkit/spatial/README.md +0 -31
  85. windkit-2.1.0rc1/windkit/spatial/_dimensions.py +0 -66
  86. windkit-2.1.0rc1/windkit/utils.py +0 -34
  87. windkit-2.1.0rc1/windkit/wind_climate/geostrophic_wind_climate.py +0 -148
  88. windkit-2.1.0rc1/windkit/workspace.py +0 -709
  89. windkit-2.1.0rc1/windkit/xarray_structures/_validate.py +0 -225
  90. windkit-2.1.0rc1/windkit/xarray_structures/empty.py +0 -79
  91. {windkit-2.1.0rc1 → windkit-2.2.0}/LICENSE +0 -0
  92. {windkit-2.1.0rc1 → windkit-2.2.0}/setup.cfg +0 -0
  93. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/cfd.py +0 -0
  94. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/color_table_roughness.json +0 -0
  95. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/CGLS-LC100.json +0 -0
  96. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/CORINE.json +0 -0
  97. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/ESA_CCI.json +0 -0
  98. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/GWA4_micro.json +0 -0
  99. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/Globcover.json +0 -0
  100. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/MODIS.json +0 -0
  101. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/NEWA_micro_v2.json +0 -0
  102. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/landcover_tables/WorldCover.json +0 -0
  103. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/loss_tables/dtu_loss_table.csv +0 -0
  104. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/map_file_header_to_epsg.json +0 -0
  105. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/map_file_header_to_epsg_read.json +0 -0
  106. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/data/uncertainty_tables/dtu_uncertainty_table.csv +0 -0
  107. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/integrations/__init__.py +0 -0
  108. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/integrations/_gwa_map_api.py +0 -0
  109. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/__init__.py +0 -0
  110. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/readers.py +0 -0
  111. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/__init__.py +0 -0
  112. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/common.py +0 -0
  113. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/tswc.py +0 -0
  114. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/wind_turbines.py +0 -0
  115. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/wtg.py +0 -0
  116. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/translate/wwc.py +0 -0
  117. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/utils.py +0 -0
  118. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/io/windio/writers.py +0 -0
  119. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/ltc/__init__.py +0 -0
  120. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/ltc/_validation.py +0 -0
  121. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/ltc/regression.py +0 -0
  122. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/ltc/scores.py +0 -0
  123. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/__init__.py +0 -0
  124. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/_helpers.py +0 -0
  125. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/color.py +0 -0
  126. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/operational_curves.py +0 -0
  127. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/roughness_rose.py +0 -0
  128. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/spatial_plots.py +0 -0
  129. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/time_series.py +0 -0
  130. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/vertical_profile.py +0 -0
  131. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/wind_rose.py +0 -0
  132. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/plot/wind_turbine.py +0 -0
  133. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_conversions.py +0 -0
  134. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_crs.py +0 -0
  135. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_cuboid.py +0 -0
  136. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_latitude.py +0 -0
  137. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_metpy_natural_neighbors.py +0 -0
  138. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_utm.py +0 -0
  139. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_vector.py +0 -0
  140. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/_vertical.py +0 -0
  141. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/spatial/decorators.py +0 -0
  142. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/__init__.py +0 -0
  143. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/landcover.py +0 -0
  144. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversion/__init__.py +0 -0
  145. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversion/_polygons_constraints.py +0 -0
  146. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversion/helper_functions.py +0 -0
  147. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/topography/map_conversion/lines_to_poly.py +0 -0
  148. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/weng_workspace.py +0 -0
  149. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/__init__.py +0 -0
  150. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_climate/_synthetic_time_series.py +0 -0
  151. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_data.py +0 -0
  152. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/__init__.py +0 -0
  153. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/wind_farm/power_curve.py +0 -0
  154. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/xarray_structures/data_structures.py +0 -0
  155. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/xarray_structures/direction.py +0 -0
  156. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/xarray_structures/sector.py +0 -0
  157. {windkit-2.1.0rc1 → windkit-2.2.0}/windkit/xarray_structures/wsbin.py +0 -0
@@ -1,4 +1,45 @@
1
1
  resource_rules:
2
+ merge_requests:
3
+ rules:
4
+ # Label merge requests as Stale after 6 months of inactivity
5
+ - name: Label stale merge requests
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
+ actions:
16
+ labels:
17
+ - Stale
18
+ comment: |
19
+ This merge request has been inactive for 6 months and has been labeled `~Stale`.
20
+
21
+ If it is still relevant, please rebase or comment to reset the
22
+ inactivity timer. Otherwise, it will be automatically closed in
23
+ 3 months.
24
+
25
+ # Close merge requests 3 months after the Stale label was added
26
+ - name: Close stale merge requests
27
+ conditions:
28
+ state: opened
29
+ date:
30
+ attribute: updated_at
31
+ condition: older_than
32
+ interval_type: months
33
+ interval: 3
34
+ labels:
35
+ - Stale
36
+ actions:
37
+ status: close
38
+ comment: |
39
+ This merge request has been inactive and is being closed
40
+ automatically per our [stale merge request policy](CONTRIBUTING.md#stale-merge-requests).
41
+
42
+ If it is still relevant, feel free to reopen it.
2
43
  issues:
3
44
  rules:
4
45
  # Label issues as Stale after 6 months of inactivity
@@ -2,7 +2,138 @@
2
2
 
3
3
  All major changes are listed here.
4
4
 
5
- ## Unreleased
5
+ ## [2.2.0 (2026-09-29)](https://docs.wasp.dk/windkit/v2.2.0/)
6
+
7
+ windkit 2.2 contains breaking changes. Read *Upgrading from 2.1* before you upgrade.
8
+
9
+ ### Highlights
10
+
11
+ * **More WAsP file formats**: `windkit.io.wasp` reads `.wtg`, `.map`, `.rsf`/`.wrg`, `.gwc` and `.wwh` files as plain dictionaries of numpy arrays, and writes `.map` and `.rsf`/`.wrg`.
12
+ * **Results from WAsP workspaces**: `windkit.Workspace` reads the wind climates, site effects, AEP and turbines stored in a WAsP workspace, including workspaces whose projects use different CRSs.
13
+ * **Radial basis function interpolation**: `interp_unstructured` and `interp_unstructured_like` gained `method="rbf"`, which, unlike `linear` and `cubic`, extrapolates beyond the source points.
14
+ * **Wind directions relative to grid north**: `bwc_from_tswc` rotates true-north wind directions to the grid north of a projected CRS given as `wind_dir_crs`, and `windkit.spatial.grid_convergence` computes the angle between the two.
15
+ * **`windkit` CLI**: `windkit configure` saves the name, email and institution that windkit stamps on datasets. windkit no longer prompts for them.
16
+
17
+ ### Upgrading from 2.1
18
+
19
+ Most code runs unchanged. Check the following:
20
+
21
+ * **Direction frequencies**: `validate_bwc`, `validate_wwc`, `validate_gwc`, `validate_geowc` and the functions that check their wind climate input now raise when `wdfreq` (`geo_wv_freq` for a geostrophic wind climate) does not sum to 1 at a location. Divide frequencies in percent by 100, or normalise them with `ds["wdfreq"] = ds["wdfreq"] / ds["wdfreq"].sum("sector")`.
22
+ * **Net AEP**: `total_loss_factor` now compounds losses, so net AEP from a loss table with more than one loss is slightly higher than in 2.1.
23
+ * **Author attributes**: datasets carry `author`, `author_email` and `institution` only if you set them, with `windkit configure` or the `WINDKIT_NAME`, `WINDKIT_EMAIL` and `WINDKIT_INSTITUTION` environment variables. windkit 2.1 and earlier still ask for any field left unset.
24
+ * **Configuration**: `windkit.config.Settings` is removed; replace `windkit.config.CONFIG` with `windkit.config.get_config()`. windkit no longer installs `pydantic`, `pydantic-settings` or `toml`, so install them yourself if your code uses them.
25
+ * **Nearest points**: `nearest_points` raises a `ValueError` when its result cannot keep the target's spatial structure, for example when two targets share a source point. Pass `windkit.spatial.to_point(target)` to get point output instead.
26
+ * **Generated data**: the `create_*` functions that generate synthetic wind climates, site effects and time series return float32 variables, no longer add a size-one `height` or `point` dimension, and give different values for the same seed. Regenerate any stored reference output.
27
+ * **Zip archives**: read a GeoPackage in a zip archive whose name lacks `.gpkg` with `spatial_format="vector"` and `file_format="gpkg"`.
28
+ * **Errors**: import the exception classes from `windkit`, e.g. `windkit.WindkitValidationError`. Some functions now raise a different exception type; see *API Breaks*.
29
+
30
+ ### New features
31
+
32
+ * `windkit.io.wasp` reads and writes more WAsP files as plain dictionaries of numpy arrays: `.wtg` (`read_wtg`, `parse_wtg_element`), `.map` (`read_map`, `write_map`, `format_map`), `.rsf`/`.wrg` (`read_rsf`, `write_rsf`, `format_rsf`), `.gwc` (`read_gwc`, `parse_gwc_element`) and `.wwh` workspaces (`read_wwh`, `parse_wwh_element`, `parse_wwh_member_element`).
33
+ * `windkit.Workspace` reads the results WAsP stored in a workspace: `get_wwc`, `get_site_effects`, `get_aep` and `get_owc_weibull_fit` return one point per site, and warn when WAsP marked the results as out of date.
34
+ * `windkit.Workspace` gained `get_wtg`, `get_mesoclimate`, `get_air_density_reference`, `get_parameters`, `get_ids` and `get_member`. The `ID` argument of the getters is optional when the workspace holds one object of that kind.
35
+ * `windkit.Workspace` takes each object's CRS from its own WAsP project, and its getters accept `crs` for workspaces that state none. Workspace format 4.01.0002 is supported.
36
+ * `ws_cdf` and `ws_freq_gt_mean` accept Weibull and time series wind climates; for these, `ws_cdf` takes `ws_bins`.
37
+ * `mean_wind_speed`, `mean_power_density`, `mean_ws_moment`, `ws_cdf` and `ws_freq_gt_mean` accept `bysector=True` for time series wind climates, binning directions into `n_sectors` sectors (default 12). They used to ignore it.
38
+ * `interp_unstructured` and `interp_unstructured_like` gained `method="rbf"`, based on scipy's `RBFInterpolator`. Unlike `linear` and `cubic`, it extrapolates beyond the source points. It interpolates horizontally only.
39
+ * `bwc_from_tswc`, `create_bwc`, `read_bwc` and windIO BWC import accept `wind_dir_crs`. `bwc_from_tswc` rotates true-north directions to the grid north of that CRS; the others record the frame of the existing directions.
40
+ * Added `windkit.spatial.grid_convergence`, which computes the grid convergence at a dataset's locations, or between two projections with `source=`.
41
+ * Added `windkit.fill_elevation_nodata`, which fills NaN and infinite cells in an elevation raster.
42
+ * `read_elevation_map`, `read_roughness_map` and `read_landcover_map` read a member of an open zip archive (`zipfile.ZipExtFile`) in the format its name gives.
43
+ * Added the `windkit configure` command, which saves the name, email and institution stamped on datasets, and `windkit.config.get_config()`, which returns them.
44
+ * `WindkitError`, `WindkitValidationError`, `WindkitFileFormatError` and `UnsupportedFileTypeError` can be imported from `windkit`.
45
+ * `polygons_to_lines` and the `.map` writers accept `snap_tolerance`.
46
+ * `nearest_points` gained `workers` for parallel queries (default 1).
47
+ * `create_bwc` gained `wsbin_width`, `weibull_A` and `weibull_k` (each Weibull parameter a value or a `(low, high)` range), and `create_geowc` gained `weibull_A` and `weibull_k`.
48
+ * `create_tswc` gained `fields`, which adds the stability variables `temp_scale` and `ustar_over_pblh`. Added `create_surface_fields`, which generates mesoscale surface fields.
49
+ * Added `create_gewc`, `create_pewc` and `create_v50_lut`, which generate synthetic inputs for extreme wind calculations.
50
+
51
+ ### Bug fixes
52
+
53
+ * `total_loss_factor` compounds losses as `prod(1 - p_i / 100)` instead of `1 - sum(p_i) / 100`, which understated the remaining energy and could go negative. `total_loss` still reports the plain sum; `loss_table_summary` shows both.
54
+ * Reading a `.map` file applies the `z` scale and offset from its header, so elevations given in feet are converted to metres. A map whose header transforms `x` and `y` raises `NotImplementedError` instead of being misplaced.
55
+ * Writing a `.map` file in a geographic CRS no longer rounds coordinates to one decimal, which moved them by up to 11 km.
56
+ * Writing a `.map` file records a CRS without an EPSG code in the header instead of leaving it empty.
57
+ * Writing a `.map` file with NaN in `elev`, `z0_left` or `z0_right` raises a `ValueError` instead of dropping those lines.
58
+ * Reading a malformed `.map` file raises a `ValueError` naming the file and line instead of returning corrupted geometry.
59
+ * `polygons_to_lines` accepts polygons with Z coordinates.
60
+ * `snap_to_layer` no longer overwrites columns named `npoints`, `npoints_old` or `done`.
61
+ * The deprecated `PolygonMap.to_line_map` works again with `complete_polys=True` and `complete_method="new"`.
62
+ * `wwc_to_file` honours `use_production=True` for `.rsf`/`.wrg` files; it always wrote power density.
63
+ * Writing `.rsf`/`.wrg` files no longer fails for heights of 100 m or more, large coordinates or a single-sector `wdfreq` of 1. `site_elev` is rounded instead of truncated, and non-integer WRG header values keep one decimal.
64
+ * Reading an `.rsf`/`.wrg` file from an open file object no longer skips its first two lines.
65
+ * `read_lib` reads rows wrapped across lines, and raises a `ValueError` for a truncated file instead of hanging.
66
+ * `read_bwc` and the `windkit.io.wasp` readers read UTF-16 files without a byte-order mark, and ignore a UTF-8 byte-order mark.
67
+ * `windkit.read_gwc` keeps the file's description, and raises a `ValueError` naming the element for a malformed `.gwc` file instead of filling in NaN or zeros.
68
+ * `windkit.read_wtg` reads `.wtg` files that omit `StationaryThrustCoEfficient`, deriving it from the thrust curve. It rejects an invalid `FormatVersion`, which made a stall-regulated turbine read as pitch-regulated.
69
+ * A GeoPackage read from a zip archive member picks its layer as for a file path, instead of taking the first one listed.
70
+ * The Weibull fit functions return NaN where the moments cannot be fitted, instead of raising (without numba) or returning meaningless values (with numba).
71
+ * The Weibull fit functions accept numpy arrays, scalars, 0-d and integer inputs with and without numba. `fit_weibull_k_sumlogm` respects `order_m_first` and `order_m_higher`, and `weibull_fit` no longer copies the attributes of the moments onto `A` and `k`.
72
+ * `quantiles=True` works for the `windkit.ltc` MCP classes, which shifted every sector by one at `predict`. `wd_to_sector(quantiles=True)` accepts arrays of any shape, and puts a direction on an inner sector edge in the upper sector.
73
+ * `ws_freq_gt_mean` of a time series skips missing samples instead of counting them as below the mean.
74
+ * `shear_exponent` and `wind_veer` raise a `WindkitValidationError` for input without a vertical profile, instead of returning an empty result or an unrelated error. Duplicate heights at a location are dropped instead of giving `inf`.
75
+ * `interp_unstructured` and `interp_unstructured_like` raise a clear `ValueError` for an unknown `method`, and for keyword arguments with `method="natural"`.
76
+ * `wspd_deficit` and `wspd_deficit_sector` have units `-` instead of `m s-1`.
77
+ * AEP and flow map datasets describe all their variables; gross AEP was described as potential AEP.
78
+ * `create_bwc` no longer shifts its histogram half a bin low, which put the mean wind speed about 6 % below what `A` and `k` imply. It warns when the wind speed axis is too short for the distribution.
79
+ * `create_geowc` honours `wsbin_width`, and its default wind speed axis (60 bins instead of 10) covers the distribution.
80
+ * `create_wasp_site_effects` no longer fills `user_def_speedups` and `obstacle_speedups` with 0 or draws a `z0meso` of 0, which made downstream results NaN or degenerate. Its speedups are drawn from narrower, plausible ranges, and points of a `point` structure at the same location share their height-independent values, such as `site_elev` and `z0meso`.
81
+ * `windkit.plot.histogram(style="interactive")` works again.
82
+ * A missing optional dependency raises an `ImportError` naming the extra to install, e.g. `pip install "windkit[plots]"`.
83
+
84
+ ### API Breaks
85
+
86
+ * Removed `windkit.config.Settings` and the conversion of `windkit.ini` files from before windkit 1.0.
87
+ * windkit no longer depends on `pydantic`, `pydantic-settings` or `toml`.
88
+ * Removed the `formatters` argument of `wwc_to_file` for `.rsf`/`.wrg` files. Unknown keyword arguments to the `.rsf`/`.wrg` readers and writers raise `TypeError`.
89
+ * `elevation_map_to_file`, `roughness_map_to_file` and `landcover_map_to_file` raise `TypeError` for `mode="a"` when writing a `.map` file.
90
+ * `mean_ws_moment` requires a scalar `moment`.
91
+ * `nearest_points` with `n_nearest > 1` requires a point target.
92
+ * `windkit.Workspace.catalogue` holds each object's XML element under `"Element"` instead of its XPath under `"XPATH"`.
93
+ * The Google Earth Engine map functions raise `ImportError` when `earthengine-api` is missing (was `ValueError`) and `RuntimeError` when Earth Engine is not authenticated. Earth Engine is initialised on first use instead of at import.
94
+ * `windkit.read_wtg` raises `lxml.etree.XMLSyntaxError` instead of `xml.etree.ElementTree.ParseError` for invalid XML (both subclass `SyntaxError`), and a `ValueError` naming the file for other malformed files.
95
+ * A `.map` file with displacement heights raises `ValueError` instead of `RuntimeError`.
96
+
97
+ ### Breaking changes
98
+
99
+ * `validate_bwc`, `validate_wwc`, `validate_gwc` and `validate_geowc` reject a wind climate whose `wdfreq` (`geo_wv_freq` for a geostrophic wind climate) does not sum to 1 at each location. All-NaN locations are exempt.
100
+ * `combine_bwcs` raises a `ValueError` for inputs with different sectors, instead of returning an invalid dataset.
101
+ * `nearest_points` keeps the target's spatial structure only when the selected source points represent it exactly, and raises a `ValueError` otherwise. It warns when `dims` leaves out a spatial coordinate of the source, and raises a `ValueError` for invalid lookups.
102
+ * `snap_to_layer`, and so `polygons_to_lines`, raise a `ValueError` when `tolerance` is at least a ring's median vertex spacing, instead of returning invalid geometry.
103
+ * `create_bwc`, `create_wwc`, `create_gwc`, `create_geowc`, `create_pwc`, `create_met_fields`, `create_tswc`, `create_tswc_pair` and `create_wasp_site_effects` keep a scalar height or a single location as given, instead of adding a size-one `height` or `point` dimension. Their data variables are float32, with the spatial dimensions first.
104
+ * Converting between spatial structures no longer adds `height` to height-independent variables such as `elevation`, `roughness`, `z0meso` and surface fields like `T2`.
105
+ * `create_wasp_site_effects` gives the effective displacement `displ` the height structure of the input, and `site_effects="all"` includes `flow_sep_height`.
106
+ * User attribution is opt-in. windkit no longer prompts for a name, email and institution, which crashed non-interactive sessions, and no longer writes them to disk. Datasets carry `author`, `author_email` and `institution` only for fields set with `windkit configure` or the `WINDKIT_NAME`, `WINDKIT_EMAIL` and `WINDKIT_INSTITUTION` environment variables.
107
+ * A zip archive member passed to `read_elevation_map`, `read_roughness_map` or `read_landcover_map` is read in the format its name gives, instead of as a GeoPackage.
108
+ * `windkit.Workspace` places an observed or generalized wind climate at the met station that holds it, instead of at the last met station in the file. Printing a workspace shows a tree of all its objects.
109
+ * The wind speed statistics name their results the same way for every wind climate type: `mean_wind_speed`, `mean_power_density`, `wind_speed_moment`, `wind_speed_cdf` and `wind_speed_fgtm`. Time series results are float64.
110
+
111
+ ### Deprecations
112
+
113
+ * `windkit.Workspace.get_object`, and the `west_east`, `south_north`, `height` and `description` arguments of `windkit.Workspace.get_gwc`, will be removed in windkit 3.0. Use the getter for the object, and set coordinates or `description` on the returned dataset.
114
+ * `windkit.config.CONFIG` will be removed in windkit 3.0; use `windkit.config.get_config()`. It now returns a frozen dataclass, so pydantic methods such as `model_dump()` are gone.
115
+
116
+ ### Performance
117
+
118
+ * `polygons_to_lines` and `snap_to_layer` are several times faster, up to 70 times on maps with many small polygons. Output is unchanged, except that near-zero-area slivers get their sides assigned robustly.
119
+ * `nearest_points` is faster and keeps the source data lazy.
120
+ * `interp_unstructured` and `interp_unstructured_like` with `nearest`, `linear` or `cubic` build one interpolator for all sectors and wind speed bins instead of one per slice.
121
+ * Reading and writing `.map` files, and writing `.rsf`/`.wrg` files, is faster.
122
+
123
+ ### Changes
124
+
125
+ * Malformed WAsP files raise `WindkitFileFormatError` naming the file. It subclasses `ValueError`, so `except ValueError` still catches it. `UnsupportedFileTypeError` now subclasses `WindkitError`.
126
+ * `read_owc` raises for malformed files, such as sectors with different numbers of bins or duplicate sectors, instead of zero-filling them.
127
+ * `windkit.read_wtg` rejects a performance table without `StationaryThrustCoEfficient` whose thrust curve is all zeros.
128
+ * `.map` files are read with any line ending and with automatic encoding detection, and written as UTF-8. Reading without `map_type` infers it, and a `map_type` that no `.map` file holds, such as `"speedup"`, raises.
129
+ * Writing a `.map` file warns when its CRS has no name in the WAsP map editor.
130
+ * `WindkitValidationError` lists every problem found in its `errors` attribute.
131
+ * `raster_plot` no longer calls `plt.show()`; display the returned figure yourself.
132
+ * `tswc_from_dataframe` and `get_tutorial_data` log instead of printing.
133
+ * `bwc_from_tswc` lists the dataset's dimensions in the order of its `wsfreq` variable.
134
+ * `affine>=3.0` is a declared dependency, and the `plots` extra requires `dash>=2.11`.
135
+ * The development environment supports `linux-aarch64` and `osx-arm64`, and needs pixi 0.68 or newer.
136
+ * The documentation has a version switcher and a page on how AI coding assistants are used in windkit's development.
6
137
 
7
138
  ## [2.1.0 (2026-04-22)](https://docs.wasp.dk/windkit/v2.1.0/)
8
139
 
@@ -77,6 +208,7 @@ Added support for reading and writing wind data in the [windIO format](https://i
77
208
 
78
209
  ### Breaking changes
79
210
 
211
+ * `bwc_from_tswc` now preserves singleton dimensions selected from TSWC inputs instead of stacking and unstacking through point structure; for example, `create_tswc(ds).isel(height=0)` now returns a BWC with the selected spatial dimensions rather than restoring `height: 1` and `stacked_point: 1`.
80
212
  * 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
213
 
82
214
  ### Deprecations
@@ -87,6 +219,7 @@ Added support for reading and writing wind data in the [windIO format](https://i
87
219
  ### Performance
88
220
 
89
221
  * 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.
222
+ * Small performance improvement in `bwc_from_tswc` due to using vectorized numpy and dropping stack/unstack pattern.
90
223
 
91
224
  ### Changes
92
225
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windkit
3
- Version: 2.1.0rc1
3
+ Version: 2.2.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
@@ -9,6 +9,7 @@ Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.11
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
+ Requires-Dist: affine>=3.0
12
13
  Requires-Dist: geopandas>=1.0
13
14
  Requires-Dist: h5netcdf
14
15
  Requires-Dist: lxml>=5.2
@@ -17,8 +18,6 @@ Requires-Dist: numpy>=2.0
17
18
  Requires-Dist: packaging>=24.1
18
19
  Requires-Dist: pandas>=2.3
19
20
  Requires-Dist: platformdirs>=4.3
20
- Requires-Dist: pydantic>=2.7
21
- Requires-Dist: pydantic-settings>=2.3
22
21
  Requires-Dist: pyproj>=3.7
23
22
  Requires-Dist: pyyaml
24
23
  Requires-Dist: rasterio>=1.4
@@ -26,12 +25,12 @@ Requires-Dist: requests
26
25
  Requires-Dist: rioxarray>=0.17
27
26
  Requires-Dist: scipy>=1.13
28
27
  Requires-Dist: shapely>=2.0
29
- Requires-Dist: toml
30
28
  Requires-Dist: xarray>=2024.9
31
29
  Provides-Extra: plots
32
30
  Requires-Dist: matplotlib>=3.9; extra == "plots"
33
31
  Requires-Dist: plotly>=5.21; extra == "plots"
34
32
  Requires-Dist: kaleido>=1.0; extra == "plots"
33
+ Requires-Dist: dash>=2.11; extra == "plots"
35
34
  Provides-Extra: performance
36
35
  Requires-Dist: numba>=0.60; extra == "performance"
37
36
  Provides-Extra: data-apis
@@ -42,8 +41,8 @@ Requires-Dist: planetary-computer>=1.0; extra == "data-apis"
42
41
  Requires-Dist: pystac-client>=0.8; extra == "data-apis"
43
42
  Requires-Dist: zarr>=3.0; extra == "data-apis"
44
43
  Provides-Extra: dev
45
- Requires-Dist: dash; extra == "dev"
46
44
  Requires-Dist: ipython; extra == "dev"
45
+ Requires-Dist: jupytext>=1.16; extra == "dev"
47
46
  Requires-Dist: nbval; extra == "dev"
48
47
  Requires-Dist: pre-commit; extra == "dev"
49
48
  Requires-Dist: pytest; extra == "dev"
@@ -53,7 +52,6 @@ Requires-Dist: windIO; extra == "dev"
53
52
  Provides-Extra: doc
54
53
  Requires-Dist: docutils; extra == "doc"
55
54
  Requires-Dist: jupyter; extra == "doc"
56
- Requires-Dist: jupyter-dash; extra == "doc"
57
55
  Requires-Dist: jupyter-sphinx; extra == "doc"
58
56
  Requires-Dist: myst-parser; extra == "doc"
59
57
  Requires-Dist: nbsphinx; extra == "doc"
@@ -93,7 +91,7 @@ pip install windkit
93
91
 
94
92
  **conda:**
95
93
  ```bash
96
- conda install -c https://conda.windenergy.dtu.dk/channel/open windkit
94
+ conda install -c https://conda.windenergy.dtu.dk/channel/open/ -c conda-forge windkit
97
95
  ```
98
96
 
99
97
  ## Quick Start
@@ -106,7 +104,7 @@ bwc = wk.read_bwc("hovsore.tab")
106
104
 
107
105
  # Calculate mean wind speed
108
106
  mean_ws = wk.mean_wind_speed(bwc)
109
- print(f"Mean wind speed: {mean_ws.values:.2f} m/s")
107
+ print(f"Mean wind speed: {mean_ws.item():.2f} m/s")
110
108
 
111
109
  # Fit Weibull distribution and convert to Weibull wind climate
112
110
  wwc = wk.weibull_fit(bwc)
@@ -114,11 +112,13 @@ wwc = wk.weibull_fit(bwc)
114
112
 
115
113
  ## Features
116
114
 
117
- - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .lib, .tab, .rsf, .wrg)
118
- - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats)
119
- - **WAsP Integration** - Read WAsP Workspace files and WAsP Engineering project files
115
+ - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .tab, .owc, .lib, .gwc, .rsf, .wrg, windIO YAML)
116
+ - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats, including WAsP .map)
117
+ - **WAsP Integration** - Read the inputs and results of WAsP workspaces (.wwh) and WAsP Engineering project files, with low-level readers for the WAsP file formats
120
118
  - **Spatial Processing** - Clip, reproject, and interpolate spatial data with full CRS support
121
119
  - **Wind Statistics** - Calculate mean wind speed, power density, Weibull parameters, and more
120
+ - **Long-Term Correction** - Measure-correlate-predict (MCP) methods for time series
121
+ - **Losses and Uncertainty** - Net AEP and exceedance values (P90) from loss and uncertainty tables
122
122
  - **Plotting** - Interactive plots with Plotly, static plots with Matplotlib
123
123
  - **Remote Data Access** - Download ERA5 reanalysis data and satellite-derived maps
124
124
 
@@ -22,7 +22,7 @@ pip install windkit
22
22
 
23
23
  **conda:**
24
24
  ```bash
25
- conda install -c https://conda.windenergy.dtu.dk/channel/open windkit
25
+ conda install -c https://conda.windenergy.dtu.dk/channel/open/ -c conda-forge windkit
26
26
  ```
27
27
 
28
28
  ## Quick Start
@@ -35,7 +35,7 @@ bwc = wk.read_bwc("hovsore.tab")
35
35
 
36
36
  # Calculate mean wind speed
37
37
  mean_ws = wk.mean_wind_speed(bwc)
38
- print(f"Mean wind speed: {mean_ws.values:.2f} m/s")
38
+ print(f"Mean wind speed: {mean_ws.item():.2f} m/s")
39
39
 
40
40
  # Fit Weibull distribution and convert to Weibull wind climate
41
41
  wwc = wk.weibull_fit(bwc)
@@ -43,11 +43,13 @@ wwc = wk.weibull_fit(bwc)
43
43
 
44
44
  ## Features
45
45
 
46
- - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .lib, .tab, .rsf, .wrg)
47
- - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats)
48
- - **WAsP Integration** - Read WAsP Workspace files and WAsP Engineering project files
46
+ - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .tab, .owc, .lib, .gwc, .rsf, .wrg, windIO YAML)
47
+ - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats, including WAsP .map)
48
+ - **WAsP Integration** - Read the inputs and results of WAsP workspaces (.wwh) and WAsP Engineering project files, with low-level readers for the WAsP file formats
49
49
  - **Spatial Processing** - Clip, reproject, and interpolate spatial data with full CRS support
50
50
  - **Wind Statistics** - Calculate mean wind speed, power density, Weibull parameters, and more
51
+ - **Long-Term Correction** - Measure-correlate-predict (MCP) methods for time series
52
+ - **Losses and Uncertainty** - Net AEP and exceedance values (P90) from loss and uncertainty tables
51
53
  - **Plotting** - Interactive plots with Plotly, static plots with Matplotlib
52
54
  - **Remote Data Access** - Download ERA5 reanalysis data and satellite-derived maps
53
55
 
@@ -17,6 +17,7 @@ classifiers = [
17
17
  dynamic = ["version"]
18
18
  requires-python = ">=3.11"
19
19
  dependencies = [
20
+ "affine>=3.0",
20
21
  "geopandas>=1.0",
21
22
  "h5netcdf",
22
23
  "lxml>=5.2",
@@ -25,8 +26,6 @@ dependencies = [
25
26
  "packaging>=24.1",
26
27
  "pandas>=2.3",
27
28
  "platformdirs>=4.3",
28
- "pydantic>=2.7",
29
- "pydantic-settings>=2.3",
30
29
  "pyproj>=3.7",
31
30
  "pyyaml",
32
31
  "rasterio>=1.4",
@@ -34,7 +33,6 @@ dependencies = [
34
33
  "rioxarray>=0.17",
35
34
  "scipy>=1.13",
36
35
  "shapely>=2.0",
37
- "toml",
38
36
  "xarray>=2024.9",
39
37
  ]
40
38
 
@@ -43,6 +41,7 @@ plots = [
43
41
  "matplotlib>=3.9",
44
42
  "plotly>=5.21",
45
43
  "kaleido>=1.0",
44
+ "dash>=2.11",
46
45
  ]
47
46
  performance = [
48
47
  "numba>=0.60",
@@ -56,8 +55,8 @@ data_apis = [
56
55
  "zarr>=3.0",
57
56
  ]
58
57
  dev = [
59
- "dash",
60
58
  "ipython",
59
+ "jupytext>=1.16",
61
60
  "nbval",
62
61
  "pre-commit",
63
62
  "pytest",
@@ -68,7 +67,6 @@ dev = [
68
67
  doc = [
69
68
  "docutils",
70
69
  "jupyter",
71
- "jupyter-dash",
72
70
  "jupyter-sphinx",
73
71
  "myst-parser",
74
72
  "nbsphinx",
@@ -85,10 +83,15 @@ doc = [
85
83
  ]
86
84
 
87
85
  [project.scripts]
86
+ windkit = "windkit._cli:main"
88
87
  split_combomap = "windkit.topography.vector_map:_split_combo"
89
88
 
90
89
  [tool.setuptools_scm]
91
90
  write_to = "windkit/_version.py"
91
+ # Between releases master is the *next minor* (2.2.0.devN after v2.1.0), so
92
+ # a dev build can satisfy a >=2.2 pin; the default scheme would call it
93
+ # 2.1.1.devN. On a vX.Y.x release branch the scheme bumps the patch instead.
94
+ version_scheme = "release-branch-semver"
92
95
 
93
96
  [tool.setuptools]
94
97
  include-package-data = true
@@ -111,8 +114,25 @@ markers = [
111
114
  "performance: marks tests as performance tests (run with '-m performance')",
112
115
  ]
113
116
 
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
117
+ # Third-party warnings raised inside upstream packages, not by windkit's own
118
+ # calls. Each entry is pinned to message, category and module so that it stops
119
+ # matching -- and the warning resurfaces for re-triage -- if upstream rewords
120
+ # or fixes it. Warnings aimed at windkit's own code are fixed at the call site,
121
+ # never listed here.
122
+ #
123
+ # pytest.warns re-emits the warnings it did not match, attributed to the
124
+ # module of warnings.WarningMessage (`warnings`, or `_py_warnings` on
125
+ # Python >= 3.14) rather than to the emitting module, so entries that can
126
+ # fire inside a pytest.warns block list those two modules as well. The cost:
127
+ # inside a pytest.warns block, the same message raised by windkit's own code
128
+ # is hidden too, so those call sites must be covered by tests outside one.
116
129
  filterwarnings = [
117
- "ignore:The 'shapely.geos' module is deprecated:DeprecationWarning:geopandas._compat",
130
+ # rioxarray (and rasterio) multiply Affine objects with `*`, which affine
131
+ # 3.0.1 deprecates in favour of `@`. rioxarray raises it from
132
+ # rioxarray._spatial_utils and rioxarray.rioxarray (0.17 through 0.22).
133
+ 'ignore:Use `@` matmul instead of `\*` mul operator:PendingDeprecationWarning:(rioxarray|warnings|_py_warnings)(\.|$)',
134
+ 'ignore:Use `@` matmul instead of `\*` mul operator:PendingDeprecationWarning:rasterio\.transform',
135
+ # pyogrio < 0.12 imports the deprecated shapely.geos module at import time;
136
+ # seen in the default-min environment only.
137
+ "ignore:The 'shapely.geos' module is deprecated:DeprecationWarning:pyogrio(\\.|$)",
118
138
  ]
@@ -9,6 +9,8 @@ __all__ = [
9
9
  "spatial",
10
10
  # Subpackage - LTC
11
11
  "ltc",
12
+ # Subpackage - io (loaded lazily on first access)
13
+ "io",
12
14
  # Wind
13
15
  "wind_speed",
14
16
  "wind_direction",
@@ -32,6 +34,12 @@ __all__ = [
32
34
  "tswc_to_file",
33
35
  "tswc_from_dataframe",
34
36
  "create_tswc_pair",
37
+ # Extreme wind climates
38
+ "create_gewc",
39
+ "create_pewc",
40
+ "create_v50_lut",
41
+ # Mesoscale surface fields
42
+ "create_surface_fields",
35
43
  # Binned wind climate
36
44
  "validate_bwc",
37
45
  "is_bwc",
@@ -76,6 +84,7 @@ __all__ = [
76
84
  "get_landcover_table",
77
85
  # Raster map
78
86
  "create_raster_map",
87
+ "fill_elevation_nodata",
79
88
  # Vector map
80
89
  "create_vector_map",
81
90
  "add_landcover_table",
@@ -147,9 +156,23 @@ __all__ = [
147
156
  # tutorial_data
148
157
  "get_tutorial_data",
149
158
  "load_tutorial_data",
159
+ # Errors
160
+ "WindkitError",
161
+ "WindkitValidationError",
162
+ "WindkitFileFormatError",
163
+ "UnsupportedFileTypeError",
150
164
  ]
151
165
 
166
+ import importlib
167
+ from typing import TYPE_CHECKING
168
+
152
169
  from windkit._version import version as __version__
170
+ from windkit._errors import (
171
+ WindkitError,
172
+ WindkitValidationError,
173
+ WindkitFileFormatError,
174
+ UnsupportedFileTypeError,
175
+ )
153
176
  from windkit import spatial
154
177
  from windkit import ltc
155
178
  from windkit.wind import (
@@ -178,6 +201,14 @@ from windkit.wind_climate.time_series_wind_climate import (
178
201
  create_tswc_pair,
179
202
  )
180
203
  from windkit.wind_climate import time_series_wind_climate # noqa: F401
204
+ from windkit.wind_climate.extreme_wind_climate import (
205
+ create_gewc,
206
+ create_pewc,
207
+ create_v50_lut,
208
+ )
209
+ from windkit.wind_climate import extreme_wind_climate # noqa: F401
210
+ from windkit.wind_climate.stability import create_surface_fields
211
+ from windkit.wind_climate import stability # noqa: F401
181
212
  from windkit.wind_climate.binned_wind_climate import (
182
213
  validate_bwc,
183
214
  is_bwc,
@@ -264,6 +295,7 @@ from windkit.topography.get_map import _get_ee_map, get_raster_map, get_vector_m
264
295
  from windkit.topography.landcover import LandCoverTable, get_landcover_table
265
296
  from windkit.topography.raster_map import (
266
297
  create_raster_map,
298
+ fill_elevation_nodata,
267
299
  )
268
300
  from windkit.topography.vector_map import create_vector_map, add_landcover_table
269
301
  from windkit.topography.topography import (
@@ -311,3 +343,21 @@ from windkit.xarray_structures.wsbin import (
311
343
  create_wsbin_coords,
312
344
  )
313
345
  from windkit.tutorial_data import get_tutorial_data, load_tutorial_data
346
+
347
+ if TYPE_CHECKING:
348
+ from windkit import io # noqa: F401
349
+
350
+ # Subpackages guaranteed reachable after `import windkit` via lazy import on
351
+ # first attribute access (PEP 562), independent of whether internal imports
352
+ # happen to load them as a side effect (#713).
353
+ _LAZY_SUBPACKAGES = ("io",)
354
+
355
+
356
+ def __getattr__(name):
357
+ if name in _LAZY_SUBPACKAGES:
358
+ return importlib.import_module(f"windkit.{name}")
359
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
360
+
361
+
362
+ def __dir__():
363
+ return sorted(set(globals()) | set(_LAZY_SUBPACKAGES))
@@ -0,0 +1,95 @@
1
+ """Command-line interface for windkit."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from windkit import config
7
+
8
+ _LABELS = {"name": "Name", "email": "Email", "institution": "Institution"}
9
+
10
+
11
+ def _configure(args) -> int:
12
+ """Write the user attribution file from flags, the existing file and prompts."""
13
+ path = config._CONFIG_FILE
14
+ existing = config._read_config_file(path)
15
+ # sys.stdin is None when stdin is closed or under pythonw
16
+ interactive = sys.stdin is not None and sys.stdin.isatty()
17
+
18
+ values = {}
19
+ missing = []
20
+ for name in config._FIELD_NAMES:
21
+ given = getattr(args, name)
22
+ if given is not None:
23
+ values[name] = config._clean(given)
24
+ elif interactive:
25
+ default = existing.get(name)
26
+ suffix = f" [{default}]" if default else ""
27
+ answer = input(f"{_LABELS[name]}{suffix}: ")
28
+ values[name] = config._clean(answer) or default
29
+ elif name in existing:
30
+ values[name] = existing[name]
31
+ else:
32
+ missing.append(name)
33
+
34
+ if missing:
35
+ flags = ", ".join(f"--{name}" for name in missing)
36
+ print(
37
+ f"windkit configure: there is no terminal to prompt for {flags}. "
38
+ "Pass them as flags; an empty value leaves a field unset.",
39
+ file=sys.stderr,
40
+ )
41
+ return 1
42
+
43
+ try:
44
+ config._write_config_file(path, config.Config(**values))
45
+ except (UnicodeEncodeError, OSError) as e:
46
+ print(
47
+ f"windkit configure: could not write {path}: {e}. The existing "
48
+ "configuration is unchanged.",
49
+ file=sys.stderr,
50
+ )
51
+ return 1
52
+ config.get_config.cache_clear()
53
+ print(f"Saved the windkit configuration to {path}")
54
+ return 0
55
+
56
+
57
+ def main(argv: list[str] | None = None) -> int:
58
+ """Run the windkit command-line interface.
59
+
60
+ Parameters
61
+ ----------
62
+ argv : list of str, optional
63
+ Command-line arguments, excluding the program name. Defaults to
64
+ ``sys.argv[1:]``.
65
+
66
+ Returns
67
+ -------
68
+ int
69
+ Exit status.
70
+ """
71
+ parser = argparse.ArgumentParser(
72
+ prog="windkit", description="windkit command-line interface"
73
+ )
74
+ subparsers = parser.add_subparsers(dest="command", required=True)
75
+
76
+ configure_parser = subparsers.add_parser(
77
+ "configure",
78
+ help="Set the user attribution stamped on windkit datasets",
79
+ description=(
80
+ "Write the name, email and institution that windkit stamps on the "
81
+ "datasets it creates as author, author_email and institution. "
82
+ "Fields not given as flags are prompted for, with the current "
83
+ "values as defaults, and an empty answer keeps the current value. "
84
+ "Without a terminal, fields not given as flags keep their current "
85
+ 'values. An empty flag value, such as --email "", leaves a field '
86
+ "unset. The WINDKIT_NAME, WINDKIT_EMAIL and WINDKIT_INSTITUTION "
87
+ "environment variables take precedence over the file."
88
+ ),
89
+ )
90
+ for name in config._FIELD_NAMES:
91
+ configure_parser.add_argument(f"--{name}", help=f"{_LABELS[name]} to stamp")
92
+ configure_parser.set_defaults(func=_configure)
93
+
94
+ args = parser.parse_args(argv)
95
+ return args.func(args)